From 691158cd0e81ac2f8acc3022eef441df53b7a580 Mon Sep 17 00:00:00 2001 From: Niko Abeler Date: Tue, 23 Aug 2022 17:59:17 +0200 Subject: [PATCH] WIP receiving webmentionsi. #7 --- README.md | 3 ++ cmd/owl-web/handler.go | 54 +++++++++++++++++++++++ cmd/owl-web/main.go | 2 + cmd/owl-web/webmention_test.go | 78 ++++++++++++++++++++++++++++++++++ post.go | 18 ++++++++ user.go | 1 + 6 files changed, 156 insertions(+) create mode 100644 cmd/owl-web/webmention_test.go diff --git a/README.md b/README.md index 1e86563..f9e3993 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,9 @@ Each directory in the `/users/` directory of a repository is considered a user. \- media/ -- Contains all media files used in the blog post. -- All files in this folder will be publicly available + \- webmention/ + \- .yml + -- Contains data for a received webmention \- meta/ \- base.html -- The template used to render all sites diff --git a/cmd/owl-web/handler.go b/cmd/owl-web/handler.go index 482cf69..cd6e47c 100644 --- a/cmd/owl-web/handler.go +++ b/cmd/owl-web/handler.go @@ -5,6 +5,7 @@ import ( "net/http" "os" "path" + "strings" "github.com/julienschmidt/httprouter" ) @@ -56,6 +57,59 @@ func userIndexHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Requ } } +func userWebmentionHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + user, err := getUserFromRepo(repo, ps) + if err != nil { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("User not found")) + return + } + err = r.ParseForm() + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Unable to parse form data")) + return + } + params := r.PostForm + target := params["target"] + source := params["source"] + if len(target) == 0 { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("No target provided")) + return + } + if len(source) == 0 { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("No source provided")) + return + } + + parts := strings.Split(target[0], "/") + if len(parts) < 2 { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("Not found")) + return + } + postId := parts[len(parts)-2] + post, err := user.GetPost(postId) + if err != nil { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("Post not found")) + return + } + err = post.AddWebmention(source[0]) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Unable to process webmention")) + return + } + + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("")) + } +} + func userRSSHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { user, err := getUserFromRepo(repo, ps) diff --git a/cmd/owl-web/main.go b/cmd/owl-web/main.go index 8ba425d..57dbe10 100644 --- a/cmd/owl-web/main.go +++ b/cmd/owl-web/main.go @@ -14,6 +14,7 @@ func Router(repo *owl.Repository) http.Handler { router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir())) router.GET("/", repoIndexHandler(repo)) router.GET("/user/:user/", userIndexHandler(repo)) + router.POST("/user/:user/webmention/", userWebmentionHandler(repo)) router.GET("/user/:user/index.xml", userRSSHandler(repo)) router.GET("/user/:user/posts/:post/", postHandler(repo)) router.GET("/user/:user/posts/:post/media/*filepath", postMediaHandler(repo)) @@ -25,6 +26,7 @@ func SingleUserRouter(repo *owl.Repository) http.Handler { router := httprouter.New() router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir())) router.GET("/", userIndexHandler(repo)) + router.POST("/webmention/", userWebmentionHandler(repo)) router.GET("/index.xml", userRSSHandler(repo)) router.GET("/posts/:post/", postHandler(repo)) router.GET("/posts/:post/media/*filepath", postMediaHandler(repo)) diff --git a/cmd/owl-web/webmention_test.go b/cmd/owl-web/webmention_test.go new file mode 100644 index 0000000..1b2db5e --- /dev/null +++ b/cmd/owl-web/webmention_test.go @@ -0,0 +1,78 @@ +package main_test + +import ( + main "h4kor/owl-blogs/cmd/owl-web" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "strings" + "testing" +) + +func TestWebmentionHandleAccepts(t *testing.T) { + repo := getTestRepo() + user, _ := repo.CreateUser("test-1") + post, _ := user.CreateNewPost("post-1") + + target := post.FullUrl() + source := "https://example.com" + data := url.Values{} + data.Set("target", target) + data.Set("source", source) + + // Create Request and Response + req, err := http.NewRequest("POST", user.UrlPath()+"webmention/", strings.NewReader(data.Encode())) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) + + if err != nil { + t.Fatal(err) + } + rr := httptest.NewRecorder() + router := main.Router(&repo) + router.ServeHTTP(rr, req) + + // Check the status code is what we expect. + if status := rr.Code; status != http.StatusAccepted { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusAccepted) + t.Errorf("Body: %v", rr.Body) + } + +} + +func TestWebmentionWrittenToPost(t *testing.T) { + repo := getTestRepo() + user, _ := repo.CreateUser("test-1") + post, _ := user.CreateNewPost("post-1") + + target := post.FullUrl() + source := "https://example.com" + data := url.Values{} + data.Set("target", target) + data.Set("source", source) + + // Create Request and Response + req, err := http.NewRequest("POST", user.UrlPath()+"webmention/", strings.NewReader(data.Encode())) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) + if err != nil { + t.Fatal(err) + } + rr := httptest.NewRecorder() + router := main.Router(&repo) + router.ServeHTTP(rr, req) + + // Check the status code is what we expect. + if status := rr.Code; status != http.StatusAccepted { + t.Errorf("handler returned wrong status code: got %v want %v", + status, http.StatusAccepted) + return + } + + if len(post.Webmentions()) != 1 { + t.Errorf("no webmention written to post") + } + +} diff --git a/post.go b/post.go index 7a2cd7d..cbb8a34 100644 --- a/post.go +++ b/post.go @@ -2,7 +2,10 @@ package owl import ( "bytes" + "crypto/sha256" + "encoding/base64" "io/ioutil" + "os" "path" "github.com/yuin/goldmark" @@ -39,6 +42,10 @@ func (post Post) MediaDir() string { return path.Join(post.Dir(), "media") } +func (post Post) WebmentionDir() string { + return path.Join(post.Dir(), "webmention") +} + func (post Post) UrlPath() string { return post.user.UrlPath() + "posts/" + post.id + "/" } @@ -139,3 +146,14 @@ func (post *Post) LoadMeta() error { post.meta = meta return nil } + +func (post *Post) AddWebmention(source string) error { + hash := sha256.Sum256([]byte(source)) + hashStr := base64.URLEncoding.EncodeToString(hash[:]) + data := "source: " + source + return os.WriteFile(path.Join(post.WebmentionDir(), hashStr+".yml"), []byte(data), 0644) +} + +func (post *Post) Webmentions() []string { + return listDir(post.WebmentionDir()) +} diff --git a/user.go b/user.go index ae4d1e2..2019361 100644 --- a/user.go +++ b/user.go @@ -150,6 +150,7 @@ func (user User) CreateNewPost(title string) (Post, error) { os.WriteFile(post.ContentFile(), []byte(initial_content), 0644) // create media dir os.Mkdir(post.MediaDir(), 0755) + os.Mkdir(post.WebmentionDir(), 0755) return post, nil }