From 8fb5f2e3a99b1fbfa5a7d7d312c0cb23a4434ad0 Mon Sep 17 00:00:00 2001 From: Thomas Lynch Date: Sun, 20 Nov 2022 11:35:17 +1100 Subject: [PATCH] Add csrf token method, sets c.CsrfToken Make posting handle multipart bodies, (set content type in api method) Implement posting, multipart form with files, etc --- app/auth.go | 26 +++++++++++++++++ app/jschan.go | 14 +++++---- app/post.go | 79 +++++++++++++++++++++++++++++++++++++-------------- example.go | 19 ++++++++++++- 4 files changed, 110 insertions(+), 28 deletions(-) diff --git a/app/auth.go b/app/auth.go index 5748671..48bcf3a 100644 --- a/app/auth.go +++ b/app/auth.go @@ -38,3 +38,29 @@ func (c *Client) Login(ctx context.Context, options *PostLoginOptions) error { return nil } + +type GetCSRFTokenResponse struct { + Token string `json:"token"` +} + +func (c *Client) GetCSRFToken(ctx context.Context) (*GetCSRFTokenResponse, error) { + + url := fmt.Sprintf("%s/csrf.json", c.BaseURL) + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + req = req.WithContext(ctx) + + res := &GetCSRFTokenResponse{} + if err := c.sendRequest(req, &res, nil); err != nil { + return nil, err + } + + c.CsrfToken = res.Token + + return res, nil + +} diff --git a/app/jschan.go b/app/jschan.go index 1099846..fe86ccf 100644 --- a/app/jschan.go +++ b/app/jschan.go @@ -49,10 +49,12 @@ func cookieHeader(rawCookies string) []*http.Cookie { func (c *Client) sendRequest(req *http.Request, v interface{}, h *ReturnHeaders) error { - if req.Method == http.MethodGet { - req.Header.Set("Content-Type", "application/json; charset=utf-8") - } else { - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + if req.Header.Get("Content-Type") == "" { + if req.Method == http.MethodGet { + req.Header.Set("Content-Type", "application/json; charset=utf-8") + } else { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } } req.Header.Set("Accept", "application/json; charset=utf-8") req.Header.Set("X-Using-XHR", "true") @@ -61,7 +63,7 @@ func (c *Client) sendRequest(req *http.Request, v interface{}, h *ReturnHeaders) req.Header.Set("Cookie", fmt.Sprintf("connect.sid=%s", c.SessionCookie)) } if c.CsrfToken != "" { - // TODO + req.Header.Set("Csrf-Token", c.CsrfToken) } res, err := c.HTTPClient.Do(req) @@ -102,7 +104,7 @@ func (c *Client) sendRequest(req *http.Request, v interface{}, h *ReturnHeaders) err3 := json.Unmarshal(body, &fullResponse) if err3 != nil { return err - } + } } } diff --git a/app/post.go b/app/post.go index d7f0ab8..84e26b2 100644 --- a/app/post.go +++ b/app/post.go @@ -1,44 +1,81 @@ package jschan import ( + "bytes" "context" "fmt" + "io" + "mime/multipart" "net/http" - "net/url" - "strings" + "net/textproto" + "os" + "path" + "strconv" ) -type PostMakePostOptions struct { - Board string - Thread int - Name string - Message string - Subject string - Email string - PostPassword string - //TODO: Files +type MakePostOptions struct { + Board string + Thread int + Name string + Message string + Subject string + Email string + PostPassword string + Files []string //Array of filenames Spoiler []string SpoilerAll bool StripFilename []string CustomFlag string - //Array for grid captcha, submitted as single param if len()==1 - Captcha []string + Captcha []string //Array for grid captcha, submitted as single param if len()==1 } -func (c *Client) MakePost(ctx context.Context, options *PostMakePostOptions) error { +func (c *Client) MakePost(ctx context.Context, options *MakePostOptions) error { - formData := url.Values{} - //TODO: post params - - endodedBody := strings.NewReader(formData.Encode()) + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + if options.Files != nil && len(options.Files) > 0 { + for _, filepath := range options.Files { + dir, fileName := path.Split(filepath) + filePath := path.Join(dir, fileName) + file, _ := os.Open(filePath) + defer file.Close() + h := make(textproto.MIMEHeader) + h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, fileName)) + h.Set("Content-Type", "image/png") + part, _ := writer.CreatePart(h) + //part, _ := writer.CreateFormFile("file", h) + io.Copy(part, file) + } + } + _ = writer.WriteField("thread", strconv.Itoa(options.Thread)) + _ = writer.WriteField("name", options.Name) + _ = writer.WriteField("message", options.Message) + _ = writer.WriteField("subject", options.Subject) + _ = writer.WriteField("email", options.Email) + _ = writer.WriteField("postpassword", options.PostPassword) + _ = writer.WriteField("customflag", options.CustomFlag) + if options.SpoilerAll == true { + _ = writer.WriteField("spoiler_all", "true") + } + for _, filename := range options.Spoiler { + _ = writer.WriteField("spoiler", filename) + } + for _, filename := range options.StripFilename { + _ = writer.WriteField("strip_filename", filename) + } + for _, answer := range options.Captcha { + _ = writer.WriteField("captcha", answer) + } + writer.Close() - url := fmt.Sprintf("%s/forms/%s/post", c.BaseURL, options.Board) + url := fmt.Sprintf("%s/forms/board/%s/post", c.BaseURL, options.Board) - req, err := http.NewRequest(http.MethodPost, url, endodedBody) + req, err := http.NewRequest(http.MethodPost, url, body) if err != nil { return err } - + // req.Header.Set("content-type", "multipart/form-data") + req.Header.Add("Content-Type", writer.FormDataContentType()) req = req.WithContext(ctx) if err := c.sendRequest(req, nil, nil); err != nil { diff --git a/example.go b/example.go index 90e8ce8..85bd488 100644 --- a/example.go +++ b/example.go @@ -20,10 +20,12 @@ func main() { err := jschanClient.Login(ctx, loginOptions) if err != nil { fmt.Println(err) - //return } if jschanClient.SessionCookie != "" { fmt.Printf("Logged in as user %s\n", loginOptions.Username) + if _, err := jschanClient.GetCSRFToken(ctx); err != nil { + fmt.Println(err) + } } overboardOptions := &jschan.GetOverboardOptions{ @@ -82,5 +84,20 @@ func main() { } fmt.Printf("Fetched /%s/ logs for date %s with %d entries\n", getLogsOptions.Board, getLogsOptions.Date.String(), len(res5)) + // makePostOptions := &jschan.MakePostOptions{ + // Board: "test", + // Thread: 277, + // Name: "Test", + // Message: ">test", + // Email: "sage", + // PostPassword: "123", + // Files: []string{"./image.png", "./image.png"}, + // } + // err6 := jschanClient.MakePost(ctx, makePostOptions) + // if err6 != nil { + // fmt.Println(err) + // return + // } + return }