Skip to content

Commit

Permalink
json: allow invalid RFC3339 dates (more than 4 year digits), add brow…
Browse files Browse the repository at this point in the history
…ser, profile, filepath fields to the jsonl formatted tool output
  • Loading branch information
srlehn committed Oct 16, 2024
1 parent c14297c commit ff3b650
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 3 deletions.
55 changes: 52 additions & 3 deletions cmd/kooky/kooky.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func main() {
domain := pflag.StringP(`domain`, `d`, ``, `cookie domain filter (partial)`)
name := pflag.StringP(`name`, `n`, ``, `cookie name filter (exact)`)
export := pflag.StringP(`export`, `o`, ``, `export cookies in netscape format`)
jsonFormat := pflag.BoolP(`json`, `j`, false, `JSON output format`)
jsonFormat := pflag.BoolP(`jsonl`, `j`, false, `JSON Lines output format`)
pflag.Parse()

cookieStores := kooky.FindAllCookieStores()
Expand Down Expand Up @@ -80,9 +80,17 @@ func main() {
} else {
for _, cookie := range cookies {
if jsonFormat != nil && *jsonFormat {
b, err := json.Marshal(cookie)
c := &jsonCookieExtra{
Cookie: cookie,
jsonCookieExtraFields: jsonCookieExtraFields{
Browser: store.Browser(),
Profile: store.Profile(),
FilePath: store.FilePath(),
},
}
b, err := json.Marshal(c)
if err != nil {
panic(err)
log.Fatalln(err)
}
fmt.Fprintf(w, "%s\n", b)
} else {
Expand Down Expand Up @@ -124,4 +132,45 @@ func trimStr(str string, length int) string {
return str[:length]
}

type jsonCookieExtra struct {
*kooky.Cookie
jsonCookieExtraFields
}

type jsonCookieExtraFields struct {
// separated for easier json marshaling
Browser string `json:"browser,omitempty"`
Profile string `json:"profile,omitempty"`
FilePath string `json:"file_path,omitempty"`
}

func (c *jsonCookieExtra) MarshalJSON() ([]byte, error) {
if c == nil {
return []byte(`null`), nil
}
b := &strings.Builder{}
b.WriteByte('{')
var hasCookieBytes bool
if c.Cookie != nil {
bc, err := c.Cookie.MarshalJSON()
if err != nil {
return nil, err
}
hasCookieBytes = len(bc) > 2
if hasCookieBytes {
b.Write(bc[1 : len(bc)-1])
}
}
be, err := json.Marshal(c.jsonCookieExtraFields)
if err != nil || len(be) <= 2 {
b.WriteByte('}')
return []byte(b.String()), nil
}
if hasCookieBytes {
b.WriteByte(',')
}
b.Write(append(be[1:len(be)-1], '}'))
return []byte(b.String()), nil
}

// TODO: "kooky -b firefox -o /dev/stdout | head" hangs
62 changes: 62 additions & 0 deletions kooky.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kooky

import (
"encoding/json"
"net/http"
"time"
)
Expand All @@ -11,3 +12,64 @@ type Cookie struct {
Creation time.Time
Container string
}

// adjustments to the json marshaling to allow dates with more than 4 year digits
// https://github.com/golang/go/issues/4556
// https://github.com/golang/go/issues/54580
// encoding/json/v2 "format"(?) might make this unnecessary

func (c *Cookie) MarshalJSON() ([]byte, error) {
if c == nil {
return []byte(`null`), nil
}
c2 := &struct {
// net/http.Cookie
Name string `json:"name"`
Value string `json:"value"`
Quoted bool `json:"quoted"`
Path string `json:"path"`
Domain string `json:"domain"`
Expires jsonTime `json:"expires"`
RawExpires string `json:"raw_expires,omitempty"`
MaxAge int `json:"max_age"`
Secure bool `json:"secure"`
HttpOnly bool `json:"http_only"`
SameSite http.SameSite `json:"same_site"`
Partitioned bool `json:"partitioned"`
Raw string `json:"raw,omitempty"`
Unparsed []string `json:"unparsed,omitempty"`
// extra fields
Creation jsonTime `json:"creation"`
Container string `json:"container,omitempty"`
}{
Name: c.Cookie.Name,
Value: c.Cookie.Value,
Quoted: c.Cookie.Quoted,
Path: c.Cookie.Path,
Domain: c.Cookie.Domain,
Expires: jsonTime{c.Cookie.Expires},
RawExpires: c.Cookie.RawExpires,
MaxAge: c.Cookie.MaxAge,
Secure: c.Cookie.Secure,
HttpOnly: c.Cookie.HttpOnly,
SameSite: c.Cookie.SameSite,
Partitioned: c.Cookie.Partitioned,
Raw: c.Cookie.Raw,
Unparsed: c.Cookie.Unparsed,
Creation: jsonTime{c.Creation},
Container: c.Container,
}
return json.Marshal(c2)
}

type jsonTime struct{ time.Time }

// MarshalJSON implements the [json.Marshaler] interface.
// The time is a quoted string in the RFC 3339 format with sub-second precision.
// the timestamp might be represented as invalid RFC 3339 if necessary (year with more than 4 digits).
func (t jsonTime) MarshalJSON() ([]byte, error) {
if b, err := t.Time.MarshalJSON(); err == nil {
return b, nil
}
return []byte(t.Time.Format(`"` + time.RFC3339 + `"`)), nil
}

0 comments on commit ff3b650

Please sign in to comment.