diff options
-rw-r--r-- | activitypub/actor.go | 34 | ||||
-rw-r--r-- | activitypub/object.go | 31 | ||||
-rw-r--r-- | activitypub/pem.go | 11 | ||||
-rw-r--r-- | config/config.go | 1 | ||||
-rw-r--r-- | db/database.go | 3 | ||||
-rw-r--r-- | main.go | 182 | ||||
-rw-r--r-- | outboxPost.go | 400 | ||||
-rw-r--r-- | post/tripcode.go (renamed from tripcode.go) | 8 | ||||
-rw-r--r-- | post/util.go | 285 | ||||
-rw-r--r-- | routes/actor.go | 381 | ||||
-rw-r--r-- | routes/admin.go | 4 | ||||
-rw-r--r-- | routes/post.go | 6 | ||||
-rw-r--r-- | routes/structs.go | 4 | ||||
-rw-r--r-- | util/blacklist.go | 76 | ||||
-rw-r--r-- | util/util.go | 26 | ||||
-rw-r--r-- | views/403.html | 10 | ||||
-rw-r--r-- | views/partials/bottom.html | 2 | ||||
-rw-r--r-- | views/partials/top.html | 4 | ||||
-rw-r--r-- | webfinger/webfinger.go | 116 |
19 files changed, 865 insertions, 719 deletions
diff --git a/activitypub/actor.go b/activitypub/actor.go index d9399ab..70681d1 100644 --- a/activitypub/actor.go +++ b/activitypub/actor.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/FChannel0/FChannel-Server/config" - "github.com/FChannel0/FChannel-Server/post" "github.com/FChannel0/FChannel-Server/util" "github.com/gofiber/fiber/v2" ) @@ -413,19 +412,12 @@ func GetActorFromDB(id string) (Actor, error) { query := `select type, id, name, preferedusername, inbox, outbox, following, followers, restricted, summary, publickeypem from actor where id=$1` - rows, err := config.DB.Query(query, id) + var publicKeyPem string + err := config.DB.QueryRow(query, id).Scan(&nActor.Type, &nActor.Id, &nActor.Name, &nActor.PreferredUsername, &nActor.Inbox, &nActor.Outbox, &nActor.Following, &nActor.Followers, &nActor.Restricted, &nActor.Summary, &publicKeyPem) if err != nil { return nActor, err } - var publicKeyPem string - defer rows.Close() - for rows.Next() { - if err := rows.Scan(&nActor.Type, &nActor.Id, &nActor.Name, &nActor.PreferredUsername, &nActor.Inbox, &nActor.Outbox, &nActor.Following, &nActor.Followers, &nActor.Restricted, &nActor.Summary, &publicKeyPem); err != nil { - return nActor, err - } - } - nActor.PublicKey, err = GetActorPemFromDB(publicKeyPem) if err != nil { return nActor, err @@ -629,6 +621,26 @@ func GetActorsFollowPostFromId(actors []string, id string) (Collection, error) { return collection, nil } +func GetActorPost(ctx *fiber.Ctx, path string) error { + collection, err := GetCollectionFromPath(config.Domain + "" + path) + if err != nil { + return err + } + + if len(collection.OrderedItems) > 0 { + enc, err := json.MarshalIndent(collection, "", "\t") + if err != nil { + return err + } + + ctx.Response().Header.Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") + _, err = ctx.Write(enc) + return err + } + + return nil +} + func GetAllActorArchiveDB(id string, offset int) (Collection, error) { var nColl Collection var result []ObjectBase @@ -814,7 +826,7 @@ func WriteActorObjectReplyToDB(obj ObjectBase) error { } func WriteActorObjectToCache(obj ObjectBase) (ObjectBase, error) { - if res, err := post.IsPostBlacklist(obj.Content); err == nil && res { + if res, err := util.IsPostBlacklist(obj.Content); err == nil && res { fmt.Println("\n\nBlacklist post blocked\n\n") return obj, nil } else if err != nil { diff --git a/activitypub/object.go b/activitypub/object.go index 692fa5b..1256fac 100644 --- a/activitypub/object.go +++ b/activitypub/object.go @@ -14,7 +14,6 @@ import ( "time" "github.com/FChannel0/FChannel-Server/config" - "github.com/FChannel0/FChannel-Server/post" "github.com/FChannel0/FChannel-Server/util" ) @@ -42,7 +41,7 @@ func CheckIfObjectOP(id string) (bool, error) { } func CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) ([]ObjectBase, *os.File, error) { - contentType, err := post.GetFileContentType(file) + contentType, err := util.GetFileContentType(file) if err != nil { return nil, nil, err } @@ -1563,7 +1562,6 @@ func AddFollower(id string, follower string) error { func WriteObjectReplyToDB(obj ObjectBase) error { for i, e := range obj.InReplyTo { - if res, err := CheckIfObjectOP(obj.Id); err == nil && !res && i == 0 { nType, err := GetObjectTypeDB(e.Id) if err != nil { @@ -1581,19 +1579,8 @@ func WriteObjectReplyToDB(obj ObjectBase) error { query := `select id from replies where id=$1 and inreplyto=$2` - rows, err := config.DB.Query(query, obj.Id, e.Id) - if err != nil { - return err - } - defer rows.Close() - var id string - rows.Next() - if err := rows.Scan(&id); err != nil { - return err - } - - if id == "" { + if err := config.DB.QueryRow(query, obj.Id, e.Id).Scan(&id); err != nil { query := `insert into replies (id, inreplyto) values ($1, $2)` _, err := config.DB.Exec(query, obj.Id, e.Id) @@ -1620,17 +1607,8 @@ func WriteObjectReplyToDB(obj ObjectBase) error { if len(obj.InReplyTo) < 1 { query := `select id from replies where id=$1 and inreplyto=$2` - rows, err := config.DB.Query(query, obj.Id, "") - if err != nil { - return err - } - defer rows.Close() - var id string - rows.Next() - rows.Scan(&id) - - if id == "" { + if err := config.DB.QueryRow(query, obj.Id, "").Scan(&id); err != nil { query := `insert into replies (id, inreplyto) values ($1, $2)` if _, err := config.DB.Exec(query, obj.Id, ""); err != nil { @@ -1688,7 +1666,7 @@ func WriteObjectReplyToLocalDB(id string, replyto string) error { } func WriteObjectToCache(obj ObjectBase) (ObjectBase, error) { - if res, err := post.IsPostBlacklist(obj.Content); err == nil && res { + if res, err := util.IsPostBlacklist(obj.Content); err == nil && res { fmt.Println("\n\nBlacklist post blocked\n\n") return obj, nil } else { @@ -1742,7 +1720,6 @@ func WriteObjectToDB(obj ObjectBase) (ObjectBase, error) { return obj, err } } - for i := range obj.Attachment { id, err := util.CreateUniqueID(obj.Actor) if err != nil { diff --git a/activitypub/pem.go b/activitypub/pem.go index ca6c068..ab225a9 100644 --- a/activitypub/pem.go +++ b/activitypub/pem.go @@ -139,16 +139,13 @@ func GetActorPemFromDB(pemID string) (PublicKeyPem, error) { query := `select id, owner, file from publickeypem where id=$1` - rows, err := config.DB.Query(query, pemID) - if err != nil { + if err := config.DB.QueryRow(query, pemID).Scan(&pem.Id, &pem.Owner, &pem.PublicKeyPem); err != nil { return pem, err } - defer rows.Close() - - rows.Next() - rows.Scan(&pem.Id, &pem.Owner, &pem.PublicKeyPem) - f, err := os.ReadFile(pem.PublicKeyPem) + dir, _ := os.Getwd() + dir = dir + "" + strings.Replace(pem.PublicKeyPem, ".", "", 1) + f, err := os.ReadFile(dir) if err != nil { return pem, err } diff --git a/config/config.go b/config/config.go index 10c029c..eb8cbdf 100644 --- a/config/config.go +++ b/config/config.go @@ -28,6 +28,7 @@ var DBPassword = GetConfigValue("dbpass", "password") var DBName = GetConfigValue("dbname", "server") var Redis = GetConfigValue("redis", "redis://localhost") var ActivityStreams = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" +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 Key string var Themes []string diff --git a/db/database.go b/db/database.go index 5cc1335..1ceac40 100644 --- a/db/database.go +++ b/db/database.go @@ -463,6 +463,7 @@ func IsReplyToOP(op string, link string) (string, bool, error) { if err != nil { return op, false, err } + defer rows.Close() var id string @@ -491,7 +492,7 @@ func GetReplyOP(link string) (string, error) { } func StartupArchive() error { - for _, e := range FollowingBoards { + for _, e := range webfinger.FollowingBoards { actor, err := activitypub.GetActorFromDB(e.Id) if err != nil { return err @@ -31,8 +31,6 @@ import ( "time" ) -var authReq = []string{"captcha", "email", "passphrase"} - var MediaHashs = make(map[string]string) var Themes []string @@ -77,7 +75,7 @@ func main() { // root actor is used to follow remote feeds that are not local //name, prefname, summary, auth requirements, restricted if config.InstanceName != "" { - if _, err = db.CreateNewBoardDB(*activitypub.CreateNewActor("", config.InstanceName, config.InstanceSummary, authReq, false)); err != nil { + if _, err = db.CreateNewBoardDB(*activitypub.CreateNewActor("", config.InstanceName, config.InstanceSummary, config.AuthReq, false)); err != nil { //panic(err) } @@ -225,13 +223,13 @@ func main() { */ app.Get("/:actor", routes.OutboxGet) - app.Get("/:actor/catalog", routes.CatalogGet) + app.Post("/:actor", routes.ActorPost) + app.Get("/:actor/catalog", routes.CatalogGet) app.Get("/:actor/:post", routes.PostGet) - app.Get("/post", routes.ActorPost) app.Get("/:actor/inbox", routes.ActorInbox) - app.Get("/:actor/outbox", routes.ActorOutbox) + app.Post("/:actor/outbox", routes.ActorOutbox) app.Get("/:actor/following", routes.ActorFollowing) app.Get("/:actor/followers", routes.ActorFollowers) @@ -259,139 +257,6 @@ func neuter(next http.Handler) http.Handler { }) } -func GetContentType(location string) string { - elements := strings.Split(location, ";") - if len(elements) > 0 { - return elements[0] - } else { - return location - } -} - -func GetActorPost(w http.ResponseWriter, path string) error { - collection, err := activitypub.GetCollectionFromPath(config.Domain + "" + path) - if err != nil { - return err - } - - if len(collection.OrderedItems) > 0 { - enc, err := json.MarshalIndent(collection, "", "\t") - if err != nil { - return err - } - - w.Header().Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") - _, err = w.Write(enc) - return err - } - - return nil -} - -func AddFollowersToActivity(activity activitypub.Activity) (activitypub.Activity, error) { - activity.To = append(activity.To, activity.Actor.Id) - - for _, e := range activity.To { - aFollowers, err := webfinger.GetActorCollection(e + "/followers") - if err != nil { - return activity, err - } - - for _, k := range aFollowers.Items { - activity.To = append(activity.To, k.Id) - } - } - - var nActivity activitypub.Activity - - for _, e := range activity.To { - var alreadyTo = false - for _, k := range nActivity.To { - if e == k || e == activity.Actor.Id { - alreadyTo = true - } - } - - if !alreadyTo { - nActivity.To = append(nActivity.To, e) - } - } - - activity.To = nActivity.To - - return activity, nil -} - -func CreateActivity(activityType string, obj activitypub.ObjectBase) (activitypub.Activity, error) { - var newActivity activitypub.Activity - - actor, err := webfinger.FingerActor(obj.Actor) - if err != nil { - return newActivity, err - } - - newActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams" - newActivity.Type = activityType - newActivity.Published = obj.Published - newActivity.Actor = &actor - newActivity.Object = &obj - - for _, e := range obj.To { - if obj.Actor != e { - newActivity.To = append(newActivity.To, e) - } - } - - for _, e := range obj.Cc { - if obj.Actor != e { - newActivity.Cc = append(newActivity.Cc, e) - } - } - - return newActivity, nil -} - -func ParseCommentForReplies(comment string, op string) ([]activitypub.ObjectBase, error) { - - re := regexp.MustCompile(`(>>(https?://[A-Za-z0-9_.:\-~]+\/[A-Za-z0-9_.\-~]+\/)(f[A-Za-z0-9_.\-~]+-)?([A-Za-z0-9_.\-~]+)?#?([A-Za-z0-9_.\-~]+)?)`) - match := re.FindAllStringSubmatch(comment, -1) - - var links []string - - for i := 0; i < len(match); i++ { - str := strings.Replace(match[i][0], ">>", "", 1) - str = strings.Replace(str, "www.", "", 1) - str = strings.Replace(str, "http://", "", 1) - str = strings.Replace(str, "https://", "", 1) - str = config.TP + "" + str - _, isReply, err := db.IsReplyToOP(op, str) - if err != nil { - return nil, err - } - - if !util.IsInStringArray(links, str) && isReply { - links = append(links, str) - } - } - - var validLinks []activitypub.ObjectBase - for i := 0; i < len(links); i++ { - _, isValid, err := webfinger.CheckValidActivity(links[i]) - if err != nil { - return nil, err - } - - if isValid { - var reply activitypub.ObjectBase - reply.Id = links[i] - reply.Published = time.Now().UTC() - validLinks = append(validLinks, reply) - } - } - - return validLinks, nil -} - func IsValidActor(id string) (activitypub.Actor, bool, error) { actor, err := webfinger.FingerActor(id) return actor, actor.Id != "", err @@ -414,16 +279,6 @@ func MakeCaptchas(total int) error { return nil } -func SupportedMIMEType(mime string) bool { - for _, e := range config.SupportedFiles { - if e == mime { - return true - } - } - - return false -} - func GetActorReported(w http.ResponseWriter, r *http.Request, id string) error { auth := r.Header.Get("Authorization") verification := strings.Split(auth, " ") @@ -484,7 +339,7 @@ func DeleteObjectRequest(id string) error { nObj.Id = id nObj.Actor = nActor.Id - activity, err := CreateActivity("Delete", nObj) + activity, err := webfinger.CreateActivity("Delete", nObj) if err != nil { return err } @@ -526,7 +381,7 @@ func DeleteObjectAndRepliesRequest(id string) error { nObj.Id = id nObj.Actor = nActor.Id - activity, err := CreateActivity("Delete", nObj) + activity, err := webfinger.CreateActivity("Delete", nObj) if err != nil { return err } @@ -618,31 +473,6 @@ func ResizeAttachmentToPreview() error { }) } -func ParseCommentForReply(comment string) (string, error) { - re := regexp.MustCompile(`(>>(https?://[A-Za-z0-9_.:\-~]+\/[A-Za-z0-9_.\-~]+\/)(f[A-Za-z0-9_.\-~]+-)?([A-Za-z0-9_.\-~]+)?#?([A-Za-z0-9_.\-~]+)?)`) - match := re.FindAllStringSubmatch(comment, -1) - - var links []string - - for i := 0; i < len(match); i++ { - str := strings.Replace(match[i][0], ">>", "", 1) - links = append(links, str) - } - - if len(links) > 0 { - _, isValid, err := webfinger.CheckValidActivity(strings.ReplaceAll(links[0], ">", "")) - if err != nil { - return "", err - } - - if isValid { - return links[0], nil - } - } - - return "", nil -} - func CreatedNeededDirectories() { if _, err := os.Stat("./public"); os.IsNotExist(err) { os.Mkdir("./public", 0755) diff --git a/outboxPost.go b/outboxPost.go index d0aa84f..677fa79 100644 --- a/outboxPost.go +++ b/outboxPost.go @@ -4,401 +4,17 @@ import ( "encoding/json" "fmt" "io/ioutil" - "mime/multipart" "net/http" - "os" - "os/exec" - "regexp" - "strings" "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/config" "github.com/FChannel0/FChannel-Server/db" - "github.com/FChannel0/FChannel-Server/post" "github.com/FChannel0/FChannel-Server/util" "github.com/FChannel0/FChannel-Server/webfinger" "github.com/gofiber/fiber/v2" _ "github.com/lib/pq" ) -func ParseOutboxRequest(ctx *fiber.Ctx) error { - //var activity activitypub.Activity - - actor, err := webfinger.GetActorFromPath(ctx.Path(), "/") - if err != nil { - return err - } - - contentType := GetContentType(ctx.Get("content-type")) - - if contentType == "multipart/form-data" || contentType == "application/x-www-form-urlencoded" { - - hasCaptcha, err := db.BoardHasAuthType(actor.Name, "captcha") - if err != nil { - return err - } - - valid, err := CheckCaptcha(ctx.FormValue("captcha")) - if err == nil && hasCaptcha && valid { - header, _ := ctx.FormFile("file") - - if header != nil { - f, _ := header.Open() - defer f.Close() - if header.Size > (7 << 20) { - return ctx.Render("403", fiber.Map{ - "message": "7MB max file size", - }) - } else if res, err := IsMediaBanned(f); err == nil && res { - //Todo add logging - fmt.Println("media banned") - return ctx.Redirect("/", 301) - } else if err != nil { - return err - } - - contentType, _ := post.GetFileContentType(f) - - if !SupportedMIMEType(contentType) { - return ctx.Render("403", fiber.Map{ - "message": "file type not supported", - }) - } - } - - var nObj = activitypub.CreateObject("Note") - nObj, err := ObjectFromForm(ctx, nObj) - if err != nil { - return err - } - - nObj.Actor = config.Domain + "/" + actor.Name - - nObj, err = activitypub.WriteObjectToDB(nObj) - if err != nil { - return err - } - - if len(nObj.To) == 0 { - if err := db.ArchivePosts(actor); err != nil { - return err - } - } - - activity, err := CreateActivity("Create", nObj) - if err != nil { - return err - } - - activity, err = AddFollowersToActivity(activity) - if err != nil { - return err - } - - go db.MakeActivityRequest(activity) - - var id string - op := len(nObj.InReplyTo) - 1 - if op >= 0 { - if nObj.InReplyTo[op].Id == "" { - id = nObj.Id - } else { - id = nObj.InReplyTo[0].Id + "|" + nObj.Id - } - } - - ctx.Response().Header.Add("status", "200") - _, err = ctx.Write([]byte(id)) - return err - } - - ctx.Response().Header.Add("status", "403") - _, err = ctx.Write([]byte("captcha could not auth")) - return err - } else { - activity, err := activitypub.GetActivityFromJson(ctx) - if err != nil { - return err - } - - if res, err := activitypub.IsActivityLocal(activity); err == nil && res { - if res := db.VerifyHeaderSignature(ctx, *activity.Actor); err == nil && !res { - ctx.Response().Header.Add("status", "403") - _, err = ctx.Write([]byte("")) - return err - } - - switch activity.Type { - case "Create": - ctx.Response().Header.Add("status", "403") - _, err = ctx.Write([]byte("")) - break - - case "Follow": - var validActor bool - var validLocalActor bool - - validActor = (activity.Object.Actor != "") - validLocalActor = (activity.Actor.Id == actor.Id) - - var rActivity activitypub.Activity - if validActor && validLocalActor { - rActivity = db.AcceptFollow(activity) - rActivity, err = db.SetActorFollowingDB(rActivity) - if err != nil { - return err - } - if err := db.MakeActivityRequest(activity); err != nil { - return err - } - } - - webfinger.FollowingBoards, err = activitypub.GetActorFollowingDB(config.Domain) - if err != nil { - return err - } - - webfinger.Boards, err = webfinger.GetBoardCollection() - if err != nil { - return err - } - break - - case "Delete": - fmt.Println("This is a delete") - ctx.Response().Header.Add("status", "403") - _, err = ctx.Write([]byte("could not process activity")) - break - - case "Note": - ctx.Response().Header.Add("status", "403") - _, err = ctx.Write([]byte("could not process activity")) - break - - case "New": - name := activity.Object.Alias - prefname := activity.Object.Name - summary := activity.Object.Summary - restricted := activity.Object.Sensitive - - actor, err := db.CreateNewBoardDB(*activitypub.CreateNewActor(name, prefname, summary, authReq, restricted)) - if err != nil { - return err - } - - if actor.Id != "" { - var board []activitypub.ObjectBase - var item activitypub.ObjectBase - var removed bool = false - - item.Id = actor.Id - for _, e := range webfinger.FollowingBoards { - if e.Id != item.Id { - board = append(board, e) - } else { - removed = true - } - } - - if !removed { - board = append(board, item) - } - - webfinger.FollowingBoards = board - webfinger.Boards, err = webfinger.GetBoardCollection() - return err - } - - ctx.Response().Header.Add("status", "403") - _, err = ctx.Write([]byte("")) - break - - default: - ctx.Response().Header.Add("status", "403") - _, err = ctx.Write([]byte("could not process activity")) - } - } else if err != nil { - return err - } else { - fmt.Println("is NOT activity") - ctx.Response().Header.Add("status", "403") - _, err = ctx.Write([]byte("could not process activity")) - return err - } - } - - return nil -} - -func ObjectFromForm(ctx *fiber.Ctx, obj activitypub.ObjectBase) (activitypub.ObjectBase, error) { - header, _ := ctx.FormFile("file") - file, _ := header.Open() - var err error - - if file != nil { - defer file.Close() - - var tempFile = new(os.File) - obj.Attachment, tempFile, err = activitypub.CreateAttachmentObject(file, header) - if err != nil { - return obj, err - } - - defer tempFile.Close() - - fileBytes, _ := ioutil.ReadAll(file) - - tempFile.Write(fileBytes) - - re := regexp.MustCompile(`image/(jpe?g|png|webp)`) - if re.MatchString(obj.Attachment[0].MediaType) { - fileLoc := strings.ReplaceAll(obj.Attachment[0].Href, config.Domain, "") - - cmd := exec.Command("exiv2", "rm", "."+fileLoc) - - if err := cmd.Run(); err != nil { - return obj, err - } - } - - obj.Preview = activitypub.CreatePreviewObject(obj.Attachment[0]) - } - - obj.AttributedTo = util.EscapeString(ctx.FormValue("name")) - obj.TripCode = util.EscapeString(ctx.FormValue("tripcode")) - obj.Name = util.EscapeString(ctx.FormValue("subject")) - obj.Content = util.EscapeString(ctx.FormValue("comment")) - obj.Sensitive = (ctx.FormValue("sensitive") != "") - - obj = ParseOptions(ctx, obj) - - var originalPost activitypub.ObjectBase - originalPost.Id = util.EscapeString(ctx.FormValue("inReplyTo")) - - obj.InReplyTo = append(obj.InReplyTo, originalPost) - - var activity activitypub.Activity - - if !util.IsInStringArray(activity.To, originalPost.Id) { - activity.To = append(activity.To, originalPost.Id) - } - - if originalPost.Id != "" { - if res, err := activitypub.IsActivityLocal(activity); err == nil && !res { - actor, err := webfinger.FingerActor(originalPost.Id) - if err != nil { - return obj, err - } - - if !util.IsInStringArray(obj.To, actor.Id) { - obj.To = append(obj.To, actor.Id) - } - } else if err != nil { - return obj, err - } - } - - replyingTo, err := ParseCommentForReplies(ctx.FormValue("comment"), originalPost.Id) - if err != nil { - return obj, err - } - - for _, e := range replyingTo { - has := false - - for _, f := range obj.InReplyTo { - if e.Id == f.Id { - has = true - break - } - } - - if !has { - obj.InReplyTo = append(obj.InReplyTo, e) - - var activity activitypub.Activity - - activity.To = append(activity.To, e.Id) - - if res, err := activitypub.IsActivityLocal(activity); err == nil && !res { - actor, err := webfinger.FingerActor(e.Id) - if err != nil { - return obj, err - } - - if !util.IsInStringArray(obj.To, actor.Id) { - obj.To = append(obj.To, actor.Id) - } - } else if err != nil { - return obj, err - } - } - } - - return obj, nil -} - -func ParseOptions(ctx *fiber.Ctx, obj activitypub.ObjectBase) activitypub.ObjectBase { - options := util.EscapeString(ctx.FormValue("options")) - if options != "" { - option := strings.Split(options, ";") - email := regexp.MustCompile(".+@.+\\..+") - wallet := regexp.MustCompile("wallet:.+") - delete := regexp.MustCompile("delete:.+") - for _, e := range option { - if e == "noko" { - obj.Option = append(obj.Option, "noko") - } else if e == "sage" { - obj.Option = append(obj.Option, "sage") - } else if e == "nokosage" { - obj.Option = append(obj.Option, "nokosage") - } else if email.MatchString(e) { - obj.Option = append(obj.Option, "email:"+e) - } else if wallet.MatchString(e) { - obj.Option = append(obj.Option, "wallet") - var wallet activitypub.CryptoCur - value := strings.Split(e, ":") - wallet.Type = value[0] - wallet.Address = value[1] - obj.Wallet = append(obj.Wallet, wallet) - } else if delete.MatchString(e) { - obj.Option = append(obj.Option, e) - } - } - } - - return obj -} - -func CheckCaptcha(captcha string) (bool, error) { - parts := strings.Split(captcha, ":") - - if strings.Trim(parts[0], " ") == "" || strings.Trim(parts[1], " ") == "" { - return false, nil - } - - path := "public/" + parts[0] + ".png" - code, err := db.GetCaptchaCodeDB(path) - if err != nil { - return false, err - } - - if code != "" { - err = db.DeleteCaptchaCodeDB(path) - if err != nil { - return false, err - } - - err = db.CreateNewCaptcha() - if err != nil { - return false, err - } - - } - - return code == strings.ToUpper(parts[1]), nil -} - func ParseInboxRequest(ctx *fiber.Ctx) error { activity, err := activitypub.GetActivityFromJson(ctx) if err != nil { @@ -596,22 +212,6 @@ func MakeActivityFollowingReq(w http.ResponseWriter, r *http.Request, activity a return respActivity.Type == "Accept", err } -func IsMediaBanned(f multipart.File) (bool, error) { - f.Seek(0, 0) - - fileBytes := make([]byte, 2048) - - _, err := f.Read(fileBytes) - if err != nil { - return true, err - } - - hash := util.HashBytes(fileBytes) - - // f.Seek(0, 0) - return db.IsHashBanned(hash) -} - func SendToFollowers(actor string, activity activitypub.Activity) error { nActor, err := activitypub.GetActorFromDB(actor) if err != nil { diff --git a/tripcode.go b/post/tripcode.go index 44651bb..3b7e48b 100644 --- a/tripcode.go +++ b/post/tripcode.go @@ -1,12 +1,12 @@ -package main +package post import ( "bytes" - "net/http" "regexp" "strings" "github.com/FChannel0/FChannel-Server/config" + "github.com/gofiber/fiber/v2" _ "github.com/lib/pq" "github.com/simia-tech/crypt" "golang.org/x/text/encoding/japanese" @@ -69,11 +69,11 @@ func TripCodeConvert(str string) string { return re.Replace(s.String()) } -func CreateNameTripCode(r *http.Request) (string, string, error) { +func CreateNameTripCode(ctx *fiber.Ctx) (string, string, error) { // TODO: to allow this to compile, this will fail for the case of the admin // this can be easily fixed when the rest of the code gets converted to fiber - input := r.FormValue("name") + input := ctx.FormValue("name") tripSecure := regexp.MustCompile("##(.+)?") diff --git a/post/util.go b/post/util.go index c4920f7..cead842 100644 --- a/post/util.go +++ b/post/util.go @@ -1,93 +1,286 @@ package post import ( + "io/ioutil" "mime/multipart" - "net/http" + "os" + "os/exec" "regexp" + "strings" + "time" + "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/util" + "github.com/FChannel0/FChannel-Server/webfinger" + "github.com/gofiber/fiber/v2" ) -type PostBlacklist struct { - Id int - Regex string -} +func ParseCommentForReplies(comment string, op string) ([]activitypub.ObjectBase, error) { + re := regexp.MustCompile(`(>>(https?://[A-Za-z0-9_.:\-~]+\/[A-Za-z0-9_.\-~]+\/)(f[A-Za-z0-9_.\-~]+-)?([A-Za-z0-9_.\-~]+)?#?([A-Za-z0-9_.\-~]+)?)`) + match := re.FindAllStringSubmatch(comment, -1) + + var links []string -func DeleteRegexBlacklistDB(id int) error { - query := `delete from postblacklist where id=$1` + for i := 0; i < len(match); i++ { + str := strings.Replace(match[i][0], ">>", "", 1) + str = strings.Replace(str, "www.", "", 1) + str = strings.Replace(str, "http://", "", 1) + str = strings.Replace(str, "https://", "", 1) + str = config.TP + "" + str + _, isReply, err := db.IsReplyToOP(op, str) + if err != nil { + return nil, err + } - _, err := config.DB.Exec(query, id) - return err + if !util.IsInStringArray(links, str) && isReply { + links = append(links, str) + } + } + + var validLinks []activitypub.ObjectBase + for i := 0; i < len(links); i++ { + _, isValid, err := webfinger.CheckValidActivity(links[i]) + if err != nil { + return nil, err + } + + if isValid { + var reply activitypub.ObjectBase + reply.Id = links[i] + reply.Published = time.Now().UTC() + validLinks = append(validLinks, reply) + } + } + + return validLinks, nil } -func GetFileContentType(out multipart.File) (string, error) { - buffer := make([]byte, 512) +func ParseCommentForReply(comment string) (string, error) { + re := regexp.MustCompile(`(>>(https?://[A-Za-z0-9_.:\-~]+\/[A-Za-z0-9_.\-~]+\/)(f[A-Za-z0-9_.\-~]+-)?([A-Za-z0-9_.\-~]+)?#?([A-Za-z0-9_.\-~]+)?)`) + match := re.FindAllStringSubmatch(comment, -1) - _, err := out.Read(buffer) - if err != nil { - return "", err + var links []string + + for i := 0; i < len(match); i++ { + str := strings.Replace(match[i][0], ">>", "", 1) + links = append(links, str) + } + + if len(links) > 0 { + _, isValid, err := webfinger.CheckValidActivity(strings.ReplaceAll(links[0], ">", "")) + if err != nil { + return "", err + } + + if isValid { + return links[0], nil + } } - out.Seek(0, 0) + return "", nil +} - contentType := http.DetectContentType(buffer) +func ParseOptions(ctx *fiber.Ctx, obj activitypub.ObjectBase) activitypub.ObjectBase { + options := util.EscapeString(ctx.FormValue("options")) + if options != "" { + option := strings.Split(options, ";") + email := regexp.MustCompile(".+@.+\\..+") + wallet := regexp.MustCompile("wallet:.+") + delete := regexp.MustCompile("delete:.+") + for _, e := range option { + if e == "noko" { + obj.Option = append(obj.Option, "noko") + } else if e == "sage" { + obj.Option = append(obj.Option, "sage") + } else if e == "nokosage" { + obj.Option = append(obj.Option, "nokosage") + } else if email.MatchString(e) { + obj.Option = append(obj.Option, "email:"+e) + } else if wallet.MatchString(e) { + obj.Option = append(obj.Option, "wallet") + var wallet activitypub.CryptoCur + value := strings.Split(e, ":") + wallet.Type = value[0] + wallet.Address = value[1] + obj.Wallet = append(obj.Wallet, wallet) + } else if delete.MatchString(e) { + obj.Option = append(obj.Option, e) + } + } + } - return contentType, nil + return obj } -func GetRegexBlacklistDB() ([]PostBlacklist, error) { - var list []PostBlacklist +func CheckCaptcha(captcha string) (bool, error) { + parts := strings.Split(captcha, ":") - query := `select id, regex from postblacklist` + if strings.Trim(parts[0], " ") == "" || strings.Trim(parts[1], " ") == "" { + return false, nil + } - rows, err := config.DB.Query(query) + path := "public/" + parts[0] + ".png" + code, err := db.GetCaptchaCodeDB(path) if err != nil { - return list, err + return false, err } - defer rows.Close() - for rows.Next() { - var temp PostBlacklist - rows.Scan(&temp.Id, &temp.Regex) + if code != "" { + err = db.DeleteCaptchaCodeDB(path) + if err != nil { + return false, err + } + + err = db.CreateNewCaptcha() + if err != nil { + return false, err + } - list = append(list, temp) } - return list, nil + return code == strings.ToUpper(parts[1]), nil } -func IsPostBlacklist(comment string) (bool, error) { - postblacklist, err := GetRegexBlacklistDB() +func IsMediaBanned(f multipart.File) (bool, error) { + f.Seek(0, 0) + fileBytes := make([]byte, 2048) + + _, err := f.Read(fileBytes) if err != nil { - return false, err + return true, err } - for _, e := range postblacklist { - re := regexp.MustCompile(e.Regex) + hash := util.HashBytes(fileBytes) + + // f.Seek(0, 0) + return db.IsHashBanned(hash) +} - if re.MatchString(comment) { - return true, nil +func SupportedMIMEType(mime string) bool { + for _, e := range config.SupportedFiles { + if e == mime { + return true } } - return false, nil + return false } -func WriteRegexBlacklistDB(regex string) error { - var re string +func ObjectFromForm(ctx *fiber.Ctx, obj activitypub.ObjectBase) (activitypub.ObjectBase, error) { - query := `select from postblacklist where regex=$1` - if err := config.DB.QueryRow(query, regex).Scan(&re); err != nil { - return err + header, _ := ctx.FormFile("file") + + var file multipart.File + + if header != nil { + file, _ = header.Open() } - if re != "" { - return nil + var err error + + if file != nil { + defer file.Close() + + var tempFile = new(os.File) + obj.Attachment, tempFile, err = activitypub.CreateAttachmentObject(file, header) + if err != nil { + return obj, err + } + + defer tempFile.Close() + + fileBytes, _ := ioutil.ReadAll(file) + + tempFile.Write(fileBytes) + + re := regexp.MustCompile(`image/(jpe?g|png|webp)`) + if re.MatchString(obj.Attachment[0].MediaType) { + fileLoc := strings.ReplaceAll(obj.Attachment[0].Href, config.Domain, "") + + cmd := exec.Command("exiv2", "rm", "."+fileLoc) + + if err := cmd.Run(); err != nil { + return obj, err + } + } + + obj.Preview = activitypub.CreatePreviewObject(obj.Attachment[0]) } - query = `insert into postblacklist (regex) values ($1)` + obj.AttributedTo = util.EscapeString(ctx.FormValue("name")) + obj.TripCode = util.EscapeString(ctx.FormValue("tripcode")) + obj.Name = util.EscapeString(ctx.FormValue("subject")) + obj.Content = util.EscapeString(ctx.FormValue("comment")) + obj.Sensitive = (ctx.FormValue("sensitive") != "") + + obj = ParseOptions(ctx, obj) + + var originalPost activitypub.ObjectBase + originalPost.Id = util.EscapeString(ctx.FormValue("inReplyTo")) + + obj.InReplyTo = append(obj.InReplyTo, originalPost) + + var activity activitypub.Activity + + if !util.IsInStringArray(activity.To, originalPost.Id) { + activity.To = append(activity.To, originalPost.Id) + } + + if originalPost.Id != "" { + if local, _ := activitypub.IsActivityLocal(activity); !local { + actor, err := webfinger.FingerActor(originalPost.Id) + if err != nil { + return obj, err + } + + if !util.IsInStringArray(obj.To, actor.Id) { + obj.To = append(obj.To, actor.Id) + } + } else if err != nil { + return obj, err + } + } + + replyingTo, err := ParseCommentForReplies(ctx.FormValue("comment"), originalPost.Id) + + if err != nil { + return obj, err + } + + for _, e := range replyingTo { + has := false + + for _, f := range obj.InReplyTo { + if e.Id == f.Id { + has = true + break + } + } + + if !has { + obj.InReplyTo = append(obj.InReplyTo, e) + + var activity activitypub.Activity + + activity.To = append(activity.To, e.Id) + + if local, err := activitypub.IsActivityLocal(activity); err == nil && !local { + actor, err := webfinger.FingerActor(e.Id) + if err != nil { + return obj, err + } + + if !util.IsInStringArray(obj.To, actor.Id) { + obj.To = append(obj.To, actor.Id) + } + } else if err != nil { + return obj, err + } + } + } - _, err := config.DB.Exec(query, regex) - return err + return obj, nil } diff --git a/routes/actor.go b/routes/actor.go index f107ed7..6e5955e 100644 --- a/routes/actor.go +++ b/routes/actor.go @@ -1,6 +1,22 @@ package routes -import "github.com/gofiber/fiber/v2" +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + + "github.com/FChannel0/FChannel-Server/activitypub" + "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/db" + "github.com/FChannel0/FChannel-Server/post" + "github.com/FChannel0/FChannel-Server/util" + "github.com/FChannel0/FChannel-Server/webfinger" + "github.com/gofiber/fiber/v2" +) func ActorInbox(c *fiber.Ctx) error { // STUB @@ -8,10 +24,211 @@ func ActorInbox(c *fiber.Ctx) error { return c.SendString("actor inbox") } -func ActorOutbox(c *fiber.Ctx) error { - // STUB +func ActorOutbox(ctx *fiber.Ctx) error { + //var activity activitypub.Activity + actor, err := webfinger.GetActorFromPath(ctx.Path(), "/") + if err != nil { + return err + } + + contentType := util.GetContentType(ctx.Get("content-type")) + + if contentType == "multipart/form-data" || contentType == "application/x-www-form-urlencoded" { + hasCaptcha, err := db.BoardHasAuthType(actor.Name, "captcha") + if err != nil { + return err + } + + valid, err := post.CheckCaptcha(ctx.FormValue("captcha")) + if err == nil && hasCaptcha && valid { + header, _ := ctx.FormFile("file") + + if header != nil { + f, _ := header.Open() + defer f.Close() + if header.Size > (7 << 20) { + return ctx.Render("403", fiber.Map{ + "message": "7MB max file size", + }) + } else if res, err := post.IsMediaBanned(f); err == nil && res { + //Todo add logging + fmt.Println("media banned") + return ctx.Redirect("/", 301) + } else if err != nil { + return err + } + + contentType, _ := util.GetFileContentType(f) + + if !post.SupportedMIMEType(contentType) { + return ctx.Render("403", fiber.Map{ + "message": "file type not supported", + }) + } + } + + var nObj = activitypub.CreateObject("Note") + nObj, err := post.ObjectFromForm(ctx, nObj) + if err != nil { + return err + } + + nObj.Actor = config.Domain + "/" + actor.Name + + nObj, err = activitypub.WriteObjectToDB(nObj) + if err != nil { + return err + } + + if len(nObj.To) == 0 { + if err := db.ArchivePosts(actor); err != nil { + return err + } + } + + activity, err := webfinger.CreateActivity("Create", nObj) + if err != nil { + return err + } + + activity, err = webfinger.AddFollowersToActivity(activity) + if err != nil { + return err + } + + go db.MakeActivityRequest(activity) + + var id string + op := len(nObj.InReplyTo) - 1 + if op >= 0 { + if nObj.InReplyTo[op].Id == "" { + id = nObj.Id + } else { + id = nObj.InReplyTo[0].Id + "|" + nObj.Id + } + } + + ctx.Response().Header.Set("Status", "200") + _, err = ctx.Write([]byte(id)) + return err + } + + ctx.Response().Header.Set("Status", "403") + _, err = ctx.Write([]byte("captcha could not auth")) + return err + } else { // json request + activity, err := activitypub.GetActivityFromJson(ctx) + if err != nil { + return err + } + + if res, err := activitypub.IsActivityLocal(activity); err == nil && res { + if res := db.VerifyHeaderSignature(ctx, *activity.Actor); err == nil && !res { + ctx.Response().Header.Set("Status", "403") + _, err = ctx.Write([]byte("")) + return err + } + + switch activity.Type { + case "Create": + ctx.Response().Header.Set("Status", "403") + _, err = ctx.Write([]byte("")) + break + + case "Follow": + var validActor bool + var validLocalActor bool + + validActor = (activity.Object.Actor != "") + validLocalActor = (activity.Actor.Id == actor.Id) + + var rActivity activitypub.Activity + if validActor && validLocalActor { + rActivity = db.AcceptFollow(activity) + rActivity, err = db.SetActorFollowingDB(rActivity) + if err != nil { + return err + } + if err := db.MakeActivityRequest(activity); err != nil { + return err + } + } + + webfinger.FollowingBoards, err = activitypub.GetActorFollowingDB(config.Domain) + if err != nil { + return err + } + + webfinger.Boards, err = webfinger.GetBoardCollection() + if err != nil { + return err + } + break + + case "Delete": + fmt.Println("This is a delete") + ctx.Response().Header.Set("Status", "403") + _, err = ctx.Write([]byte("could not process activity")) + break + + case "Note": + ctx.Response().Header.Set("Satus", "403") + _, err = ctx.Write([]byte("could not process activity")) + break + + case "New": + name := activity.Object.Alias + prefname := activity.Object.Name + summary := activity.Object.Summary + restricted := activity.Object.Sensitive + + actor, err := db.CreateNewBoardDB(*activitypub.CreateNewActor(name, prefname, summary, config.AuthReq, restricted)) + if err != nil { + return err + } + + if actor.Id != "" { + var board []activitypub.ObjectBase + var item activitypub.ObjectBase + var removed bool = false + + item.Id = actor.Id + for _, e := range webfinger.FollowingBoards { + if e.Id != item.Id { + board = append(board, e) + } else { + removed = true + } + } + + if !removed { + board = append(board, item) + } + + webfinger.FollowingBoards = board + webfinger.Boards, err = webfinger.GetBoardCollection() + return err + } + + ctx.Response().Header.Set("Status", "403") + _, err = ctx.Write([]byte("")) + break + + default: + ctx.Response().Header.Set("status", "403") + _, err = ctx.Write([]byte("could not process activity")) + } + } else if err != nil { + return err + } else { + fmt.Println("is NOT activity") + ctx.Response().Header.Set("Status", "403") + _, err = ctx.Write([]byte("could not process activity")) + return err + } + } - return c.SendString("actor outbox") + return nil } func ActorFollowing(c *fiber.Ctx) error { @@ -38,8 +255,158 @@ func ActorArchive(c *fiber.Ctx) error { return c.SendString("actor archive") } -func ActorPost(c *fiber.Ctx) error { - // STUB +func ActorPost(ctx *fiber.Ctx) error { + header, _ := ctx.FormFile("file") + + if ctx.FormValue("inReplyTo") == "" && header == nil { + return ctx.Render("403", fiber.Map{ + "message": "Media is required for new posts", + }) + } + + var file multipart.File + + if header != nil { + file, _ = header.Open() + } + + if file != nil && header.Size > (7<<20) { + return ctx.Render("403", fiber.Map{ + "message": "7MB max file size", + }) + } + + if is, _ := util.IsPostBlacklist(ctx.FormValue("comment")); is { + errors.New("\n\nBlacklist post blocked\n\n") + return ctx.Redirect("/", 301) + } + + if ctx.FormValue("inReplyTo") == "" || file == nil { + if ctx.FormValue("comment") == "" && ctx.FormValue("subject") == "" { + return ctx.Render("403", fiber.Map{ + "message": "Comment or Subject required", + }) + } + } + + if len(ctx.FormValue("comment")) > 2000 { + return ctx.Render("403", fiber.Map{ + "message": "Comment limit 2000 characters", + }) + } + + if len(ctx.FormValue("subject")) > 100 || len(ctx.FormValue("name")) > 100 || len(ctx.FormValue("options")) > 100 { + return ctx.Render("403", fiber.Map{ + "message": "Name, Subject or Options limit 100 characters", + }) + } + + if ctx.FormValue("captcha") == "" { + return ctx.Render("403", fiber.Map{ + "message": "Incorrect Captcha", + }) + } + + b := bytes.Buffer{} + we := multipart.NewWriter(&b) + + if file != nil { + var fw io.Writer + + fw, err := we.CreateFormFile("file", header.Filename) + + if err != nil { + errors.New("error with form file create") + } + _, err = io.Copy(fw, file) + + if err != nil { + errors.New("error with form file copy") + } + } + + reply, _ := post.ParseCommentForReply(ctx.FormValue("comment")) + + form, _ := ctx.MultipartForm() + + for key, r0 := range form.Value { + if key == "captcha" { + err := we.WriteField(key, ctx.FormValue("captchaCode")+":"+ctx.FormValue("captcha")) + if err != nil { + errors.New("error with writing captcha field") + } + } else if key == "name" { + name, tripcode, _ := post.CreateNameTripCode(ctx) + + err := we.WriteField(key, name) + if err != nil { + errors.New("error with writing name field") + } + + err = we.WriteField("tripcode", tripcode) + if err != nil { + errors.New("error with writing tripcode field") + } + } else { + err := we.WriteField(key, r0[0]) + if err != nil { + errors.New("error with writing field") + } + } + } + + if ctx.FormValue("inReplyTo") == "" && reply != "" { + err := we.WriteField("inReplyTo", reply) + if err != nil { + errors.New("error with writing inReplyTo field") + } + } + + we.Close() + + sendTo := ctx.FormValue("sendTo") + + req, err := http.NewRequest("POST", sendTo, &b) + + if err != nil { + errors.New("error with post form req") + } + + req.Header.Set("Content-Type", we.FormDataContentType()) + + resp, err := util.RouteProxy(req) + + if err != nil { + errors.New("error with post form resp") + } + + defer resp.Body.Close() + + if resp.StatusCode == 200 { + + body, _ := ioutil.ReadAll(resp.Body) + + var obj activitypub.ObjectBase + + obj = post.ParseOptions(ctx, obj) + for _, e := range obj.Option { + if e == "noko" || e == "nokosage" { + return ctx.Redirect(config.Domain+"/"+ctx.FormValue("boardName")+"/"+util.ShortURL(ctx.FormValue("sendTo"), string(body)), 301) + } + } + + if ctx.FormValue("returnTo") == "catalog" { + return ctx.Redirect(config.Domain+"/"+ctx.FormValue("boardName")+"/catalog", 301) + } else { + return ctx.Redirect(config.Domain+"/"+ctx.FormValue("boardName"), 301) + } + } + + if resp.StatusCode == 403 { + return ctx.Render("403", fiber.Map{ + "message": "Incorrect Captcha", + }) + } - return c.SendString("actor post") + return ctx.Redirect(config.Domain+"/"+ctx.FormValue("boardName"), 301) } diff --git a/routes/admin.go b/routes/admin.go index c714b06..a2f7cd2 100644 --- a/routes/admin.go +++ b/routes/admin.go @@ -3,7 +3,7 @@ package routes import ( "github.com/FChannel0/FChannel-Server/config" "github.com/FChannel0/FChannel-Server/db" - "github.com/FChannel0/FChannel-Server/post" + "github.com/FChannel0/FChannel-Server/util" "github.com/FChannel0/FChannel-Server/webfinger" "github.com/gofiber/fiber/v2" ) @@ -54,7 +54,7 @@ func AdminIndex(ctx *fiber.Ctx) error { adminData.Board.Post.Actor = actor.Id - adminData.PostBlacklist, _ = post.GetRegexBlacklistDB() + adminData.PostBlacklist, _ = util.GetRegexBlacklistDB() adminData.Themes = &config.Themes diff --git a/routes/post.go b/routes/post.go index 016e533..64df600 100644 --- a/routes/post.go +++ b/routes/post.go @@ -17,6 +17,12 @@ func PostGet(ctx *fiber.Ctx) error { return err } + // this is a activitpub json request return json instead of html page + if activitypub.AcceptActivity(ctx.Get("Accept")) { + activitypub.GetActorPost(ctx, ctx.Path()) + return nil + } + postId := ctx.Params("post") inReplyTo := actor.Id + "/" + postId diff --git a/routes/structs.go b/routes/structs.go index 8cfb6ee..7c6e980 100644 --- a/routes/structs.go +++ b/routes/structs.go @@ -3,7 +3,7 @@ package routes import ( "github.com/FChannel0/FChannel-Server/activitypub" "github.com/FChannel0/FChannel-Server/db" - "github.com/FChannel0/FChannel-Server/post" + "github.com/FChannel0/FChannel-Server/util" "github.com/FChannel0/FChannel-Server/webfinger" ) @@ -40,7 +40,7 @@ type AdminPage struct { Reported []db.Report Domain string IsLocal bool - PostBlacklist []post.PostBlacklist + PostBlacklist []util.PostBlacklist AutoSubscribe bool Themes *[]string diff --git a/util/blacklist.go b/util/blacklist.go new file mode 100644 index 0000000..5368037 --- /dev/null +++ b/util/blacklist.go @@ -0,0 +1,76 @@ +package util + +import ( + "regexp" + + "github.com/FChannel0/FChannel-Server/config" +) + +type PostBlacklist struct { + Id int + Regex string +} + +func DeleteRegexBlacklistDB(id int) error { + query := `delete from postblacklist where id=$1` + + _, err := config.DB.Exec(query, id) + return err +} + +func GetRegexBlacklistDB() ([]PostBlacklist, error) { + var list []PostBlacklist + + query := `select id, regex from postblacklist` + + rows, err := config.DB.Query(query) + if err != nil { + return list, err + } + + defer rows.Close() + for rows.Next() { + var temp PostBlacklist + rows.Scan(&temp.Id, &temp.Regex) + + list = append(list, temp) + } + + return list, nil +} + +func IsPostBlacklist(comment string) (bool, error) { + postblacklist, err := GetRegexBlacklistDB() + + if err != nil { + return false, err + } + + for _, e := range postblacklist { + re := regexp.MustCompile(e.Regex) + + if re.MatchString(comment) { + return true, nil + } + } + + return false, nil +} + +func WriteRegexBlacklistDB(regex string) error { + var re string + + query := `select from postblacklist where regex=$1` + if err := config.DB.QueryRow(query, regex).Scan(&re); err != nil { + return err + } + + if re != "" { + return nil + } + + query = `insert into postblacklist (regex) values ($1)` + + _, err := config.DB.Exec(query, regex) + return err +} diff --git a/util/util.go b/util/util.go index 381db94..ade5eae 100644 --- a/util/util.go +++ b/util/util.go @@ -4,6 +4,8 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "mime/multipart" + "net/http" "os" "regexp" "strings" @@ -225,3 +227,27 @@ func CreateUniqueID(actor string) (string, error) { return newID, nil } + +func GetFileContentType(out multipart.File) (string, error) { + buffer := make([]byte, 512) + + _, err := out.Read(buffer) + if err != nil { + return "", err + } + + out.Seek(0, 0) + + contentType := http.DetectContentType(buffer) + + return contentType, nil +} + +func GetContentType(location string) string { + elements := strings.Split(location, ";") + if len(elements) > 0 { + return elements[0] + } else { + return location + } +} diff --git a/views/403.html b/views/403.html index 13d3cea..21ee188 100644 --- a/views/403.html +++ b/views/403.html @@ -1,7 +1,7 @@ <div class="box2"> - <h1>403</h1> - - <p> - Click <a href="/">here</a> to return to the index. - </p> + <h1>403</h1> + <p>{{ .message }}</p> + <p> + Click <a href="/">here</a> to return to the index. + </p> </div> diff --git a/views/partials/bottom.html b/views/partials/bottom.html index 35e8c4a..f995cf2 100644 --- a/views/partials/bottom.html +++ b/views/partials/bottom.html @@ -3,7 +3,7 @@ <span id="reply-header-text">...</span> <div id="reply-close" style="display: inline-block; float: right;"><a href="javascript:closeReply()">[X]</a></div> </div> - <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="reply-post" action="/post" method="post" enctype="multipart/form-data"> + <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="reply-post" action="/{{ .Board.Name }}" method="post" enctype="multipart/form-data"> <input id="reply-name" name="name" type="text" placeholder="Name" maxlength="100"> <input id="reply-options" name="options" type="text" placeholder="Options" maxlength="100"> <textarea id="reply-comment" name="comment" maxlength="2000" oninput="sessionStorage.setItem('element-reply-comment', document.getElementById('reply-comment').value)"></textarea> diff --git a/views/partials/top.html b/views/partials/top.html index 032e86b..6e3bc4b 100644 --- a/views/partials/top.html +++ b/views/partials/top.html @@ -9,7 +9,7 @@ <h3 id="newpostbtn" state="0" style="display: none; margin-bottom:100px;">[<a href="javascript:startNewPost()">Start a New Thread</a>]</h3> {{ end }} <!-- end if inreplyto--> <div id="newpost"> - <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="new-post" action="/post" method="post" enctype="multipart/form-data"> + <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="new-post" action="/{{ .Board.Name }}" method="post" enctype="multipart/form-data"> <table id="postForm"> <tr> <tr> @@ -66,7 +66,7 @@ {{ end }} <!-- end if inreplyto--> {{ $len := len .Posts }} <div id="newpost"> - <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="new-post" action="/post" method="post" enctype="multipart/form-data"> + <form onsubmit="sessionStorage.setItem('element-closed-reply', true)" id="new-post" action="/{{ .Board.Name }}" method="post" enctype="multipart/form-data"> <table id="postForm"> <tr> <tr> diff --git a/webfinger/webfinger.go b/webfinger/webfinger.go index 210a3d6..7e58d00 100644 --- a/webfinger/webfinger.go +++ b/webfinger/webfinger.go @@ -77,27 +77,23 @@ func FingerActor(path string) (activitypub.Actor, error) { if ActorCache[actor+"@"+instance].Id != "" { nActor = ActorCache[actor+"@"+instance] - return nActor, nil - } + } else { + r, _ := FingerRequest(actor, instance) - r, err := FingerRequest(actor, instance) - if err != nil { - return nActor, err - } + if r != nil && r.StatusCode == 200 { + defer r.Body.Close() - if r != nil && r.StatusCode == 200 { - defer r.Body.Close() + body, _ := ioutil.ReadAll(r.Body) - body, _ := ioutil.ReadAll(r.Body) + json.Unmarshal(body, &nActor) + // if err := json.Unmarshal(body, &nActor); err != nil { + // return nActor, err + // } - if err := json.Unmarshal(body, &nActor); err != nil { - return nActor, err + ActorCache[actor+"@"+instance] = nActor } - - ActorCache[actor+"@"+instance] = nActor } - // TODO: this just falls through and returns a blank Actor object. do something? return nActor, nil } @@ -105,14 +101,14 @@ func FingerRequest(actor string, instance string) (*http.Response, error) { acct := "acct:" + actor + "@" + instance // TODO: respect https - req, err := http.NewRequest("GET", "http://"+instance+"/.well-known/webfinger?resource="+acct, nil) - if err != nil { - return nil, err - } + req, _ := http.NewRequest("GET", "http://"+instance+"/.well-known/webfinger?resource="+acct, nil) + // if err != nil { + // return nil, err + // } resp, err := util.RouteProxy(req) if err != nil { - return resp, err + return resp, nil } var finger Webfinger @@ -122,23 +118,24 @@ func FingerRequest(actor string, instance string) (*http.Response, error) { body, _ := ioutil.ReadAll(resp.Body) - if err := json.Unmarshal(body, &finger); err != nil { - return resp, err - } + json.Unmarshal(body, &finger) + // if err := json.Unmarshal(body, &finger); err != nil { + // return resp, err + // } } if len(finger.Links) > 0 { for _, e := range finger.Links { if e.Type == "application/activity+json" { - req, err := http.NewRequest("GET", e.Href, nil) - if err != nil { - return resp, err - } + req, _ := http.NewRequest("GET", e.Href, nil) + // if err != nil { + // return resp, err + // } req.Header.Set("Accept", config.ActivityStreams) - resp, err := util.RouteProxy(req) - return resp, err + resp, _ := util.RouteProxy(req) + return resp, nil } } } @@ -179,3 +176,66 @@ func CheckValidActivity(id string) (activitypub.Collection, bool, error) { return respCollection, false, nil } + +func CreateActivity(activityType string, obj activitypub.ObjectBase) (activitypub.Activity, error) { + var newActivity activitypub.Activity + + actor, err := FingerActor(obj.Actor) + if err != nil { + return newActivity, err + } + + newActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams" + newActivity.Type = activityType + newActivity.Published = obj.Published + newActivity.Actor = &actor + newActivity.Object = &obj + + for _, e := range obj.To { + if obj.Actor != e { + newActivity.To = append(newActivity.To, e) + } + } + + for _, e := range obj.Cc { + if obj.Actor != e { + newActivity.Cc = append(newActivity.Cc, e) + } + } + + return newActivity, nil +} + +func AddFollowersToActivity(activity activitypub.Activity) (activitypub.Activity, error) { + activity.To = append(activity.To, activity.Actor.Id) + + for _, e := range activity.To { + aFollowers, err := GetActorCollection(e + "/followers") + if err != nil { + return activity, err + } + + for _, k := range aFollowers.Items { + activity.To = append(activity.To, k.Id) + } + } + + var nActivity activitypub.Activity + + for _, e := range activity.To { + var alreadyTo = false + for _, k := range nActivity.To { + if e == k || e == activity.Actor.Id { + alreadyTo = true + } + } + + if !alreadyTo { + nActivity.To = append(nActivity.To, e) + } + } + + activity.To = nActivity.To + + return activity, nil +} |