package route import ( "encoding/json" "fmt" "html/template" "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/post" "github.com/FChannel0/FChannel-Server/util" "github.com/FChannel0/FChannel-Server/webfinger" "github.com/gofiber/fiber/v2" "github.com/gofiber/template/html" ) func GetThemeCookie(c *fiber.Ctx) string { cookie := c.Cookies("theme") if cookie != "" { cookies := strings.SplitN(cookie, "=", 2) return cookies[0] } return "default" } func WantToServeCatalog(actorName string) (activitypub.Collection, bool, error) { var collection activitypub.Collection serve := false actor, err := activitypub.GetActorByNameFromDB(actorName) if err != nil { return collection, false, util.MakeError(err, "WantToServeCatalog") } if actor.Id != "" { collection, err = actor.GetCatalogCollection() if err != nil { return collection, false, util.MakeError(err, "WantToServeCatalog") } collection.Actor = actor return collection, true, nil } return collection, serve, nil } func WantToServeArchive(actorName string) (activitypub.Collection, bool, error) { var collection activitypub.Collection serve := false actor, err := activitypub.GetActorByNameFromDB(actorName) if err != nil { return collection, false, util.MakeError(err, "WantToServeArchive") } if actor.Id != "" { collection, err = actor.GetCollectionType("Archive") if err != nil { return collection, false, util.MakeError(err, "WantToServeArchive") } collection.Actor = actor return collection, true, nil } return collection, serve, nil } func GetActorPost(ctx *fiber.Ctx, path string) error { obj := activitypub.ObjectBase{Id: config.Domain + "" + path} collection, err := obj.GetCollectionFromPath() if err != nil { return util.MakeError(err, "GetActorPost") } if len(collection.OrderedItems) > 0 { enc, err := json.MarshalIndent(collection, "", "\t") if err != nil { return util.MakeError(err, "GetActorPost") } ctx.Response().Header.Set("Content-Type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") _, err = ctx.Write(enc) return util.MakeError(err, "GetActorPost") } return nil } func ParseOutboxRequest(ctx *fiber.Ctx, actor activitypub.Actor) error { contentType := util.GetContentType(ctx.Get("content-type")) if contentType == "multipart/form-data" || contentType == "application/x-www-form-urlencoded" { hasCaptcha, err := util.BoardHasAuthType(actor.Name, "captcha") if err != nil { return util.MakeError(err, "ParseOutboxRequest") } 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) { ctx.Response().Header.SetStatusCode(403) _, err := ctx.Write([]byte("7MB max file size")) return util.MakeError(err, "ParseOutboxRequest") } else if isBanned, err := post.IsMediaBanned(f); err == nil && isBanned { config.Log.Println("media banned") ctx.Response().Header.SetStatusCode(403) _, err := ctx.Write([]byte("")) return util.MakeError(err, "ParseOutboxRequest") } else if err != nil { return util.MakeError(err, "ParseOutboxRequest") } contentType, _ := util.GetFileContentType(f) if !post.SupportedMIMEType(contentType) { ctx.Response().Header.SetStatusCode(403) _, err := ctx.Write([]byte("file type not supported")) return util.MakeError(err, "ParseOutboxRequest") } } var nObj = activitypub.CreateObject("Note") nObj, err := post.ObjectFromForm(ctx, nObj) if err != nil { return util.MakeError(err, "ParseOutboxRequest") } nObj.Actor = config.Domain + "/" + actor.Name nObj, err = nObj.Write() if err != nil { return util.MakeError(err, "ParseOutboxRequest") } if len(nObj.To) == 0 { if err := actor.ArchivePosts(); err != nil { return util.MakeError(err, "ParseOutboxRequest") } } activity, err := nObj.CreateActivity("Create") if err != nil { return util.MakeError(err, "ParseOutboxRequest") } activity, err = activity.AddFollowersTo() if err != nil { return util.MakeError(err, "ParseOutboxRequest") } go activity.MakeRequestInbox() 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 util.MakeError(err, "ParseOutboxRequest") } ctx.Response().Header.Set("Status", "403") _, err = ctx.Write([]byte("captcha could not auth")) return util.MakeError(err, "") } else { // json request activity, err := activitypub.GetActivityFromJson(ctx) if err != nil { return util.MakeError(err, "ParseOutboxRequest") } if res, err := activity.IsLocal(); err == nil && res { if res := activity.Actor.VerifyHeaderSignature(ctx); err == nil && !res { ctx.Response().Header.Set("Status", "403") _, err = ctx.Write([]byte("")) return util.MakeError(err, "ParseOutboxRequest") } switch activity.Type { case "Create": ctx.Response().Header.Set("Status", "403") _, err = ctx.Write([]byte("")) break case "Follow": validActor := (activity.Object.Actor != "") validLocalActor := (activity.Actor.Id == actor.Id) var rActivity activitypub.Activity if validActor && validLocalActor { rActivity = activity.AcceptFollow() rActivity, err = rActivity.SetActorFollowing() if err != nil { return util.MakeError(err, "ParseOutboxRequest") } if err := activity.MakeRequestInbox(); err != nil { return util.MakeError(err, "ParseOutboxRequest") } } actor, _ := activitypub.GetActorFromDB(config.Domain) webfinger.FollowingBoards, err = actor.GetFollowing() if err != nil { return util.MakeError(err, "ParseOutboxRequest") } webfinger.Boards, err = webfinger.GetBoardCollection() if err != nil { return util.MakeError(err, "ParseOutboxRequest") } break case "Delete": config.Log.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.CreateNewBoard(*activitypub.CreateNewActor(name, prefname, summary, config.AuthReq, restricted)) if err != nil { return util.MakeError(err, "ParseOutboxRequest") } 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 util.MakeError(err, "ParseOutboxRequest") } 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 util.MakeError(err, "ParseOutboxRequest") } else { config.Log.Println("is NOT activity") ctx.Response().Header.Set("Status", "403") _, err = ctx.Write([]byte("could not process activity")) return util.MakeError(err, "ParseOutboxRequest") } } return nil } func TemplateFunctions(engine *html.Engine) { engine.AddFunc("mod", func(i, j int) bool { return i%j == 0 }) engine.AddFunc("sub", func(i, j int) int { return i - j }) engine.AddFunc("add", func(i, j int) int { return i + j }) engine.AddFunc("unixtoreadable", func(u int) string { return time.Unix(int64(u), 0).Format("Jan 02, 2006") }) engine.AddFunc("timeToReadableLong", func(t time.Time) string { return t.Format("01/02/06(Mon)15:04:05") }) engine.AddFunc("timeToUnix", func(t time.Time) string { return fmt.Sprint(t.Unix()) }) engine.AddFunc("proxy", util.MediaProxy) // previously short engine.AddFunc("shortURL", util.ShortURL) engine.AddFunc("parseAttachment", post.ParseAttachment) engine.AddFunc("parseContent", post.ParseContent) engine.AddFunc("shortImg", util.ShortImg) engine.AddFunc("convertSize", util.ConvertSize) engine.AddFunc("isOnion", util.IsOnion) engine.AddFunc("parseReplyLink", func(actorId string, op string, id string, content string) template.HTML { actor, _ := activitypub.FingerActor(actorId) title := strings.ReplaceAll(post.ParseLinkTitle(actor.Id+"/", op, content), `/\<`, ">") link := ">>" + util.ShortURL(actor.Outbox, id) + "" return template.HTML(link) }) engine.AddFunc("shortExcerpt", func(post activitypub.ObjectBase) string { var returnString string if post.Name != "" { returnString = post.Name + "| " + post.Content } else { returnString = post.Content } re := regexp.MustCompile(`(^(.|\r\n|\n){100})`) match := re.FindStringSubmatch(returnString) if len(match) > 0 { returnString = match[0] + "..." } re = regexp.MustCompile(`(^.+\|)`) match = re.FindStringSubmatch(returnString) if len(match) > 0 { returnString = strings.Replace(returnString, match[0], ""+match[0]+"", 1) returnString = strings.Replace(returnString, "|", ":", 1) } return returnString }) }