Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
davidnewhall committed Nov 21, 2021
1 parent 4e1ce06 commit 88fcce2
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 52 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2019 Go Lift - Building Strong Go Tools
Copyright (c) 2019-2022 Go Lift - Building Strong Go Tools
Copyright (c) 2018 David Newhall II

Permission is hereby granted, free of charge, to any person obtaining a copy
Expand Down
127 changes: 83 additions & 44 deletions deluge.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package deluge

import (
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"encoding/json"
Expand All @@ -17,24 +18,45 @@ import (
"golang.org/x/net/publicsuffix"
)

// Custom errors.
var (
ErrInvalidVersion = fmt.Errorf("invalid data returned while checking version")
ErrDelugeError = fmt.Errorf("deluge error")
ErrAuthFailed = fmt.Errorf("authentication failed")
)

type Client struct {
*http.Client
cookie bool
}

// Deluge is what you get for providing a password.
type Deluge struct {
*http.Client
URL string
*Client
*Config
auth string
id int
Version string // Currently unused, for display purposes only.
Backends map[string]Backend // Currently unused, for display purposes only.
DebugLog func(msg string, fmt ...interface{})
}

// NewNoAuth returns a Deluge object without authenticating or trying to connect.
func NewNoAuth(config *Config) (*Deluge, error) {
return newConfig(config, false)
}

// New creates a http.Client with authenticated cookies.
// Used to make additional, authenticated requests to the APIs.
func New(config *Config) (*Deluge, error) {
return newConfig(config, true)
}

func newConfig(config *Config, login bool) (*Deluge, error) {
// The cookie jar is used to auth Deluge.
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
return nil, fmt.Errorf("cookiejar.New(publicsuffix): %v", err)
return nil, fmt.Errorf("cookiejar.New(publicsuffix): %w", err)
}

if !strings.HasSuffix(config.URL, "/") {
Expand All @@ -51,18 +73,24 @@ func New(config *Config) (*Deluge, error) {
}

deluge := &Deluge{
URL: config.URL,
Config: config,
auth: config.HTTPUser,
Backends: make(map[string]Backend),
DebugLog: config.DebugLog,
Client: &http.Client{
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.VerifySSL}},
Jar: jar,
Timeout: config.Timeout.Round(time.Millisecond),
Client: &Client{
Client: &http.Client{
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.VerifySSL}}, //nolint:gosec
Jar: jar,
Timeout: config.Timeout.Round(time.Millisecond),
},
},
}

if err := deluge.Login(config.Password); err != nil {
if !login {
return deluge, nil
}

if err := deluge.Login(); err != nil {
return deluge, err
}

Expand All @@ -76,33 +104,35 @@ func New(config *Config) (*Deluge, error) {
}

// Login sets the cookie jar with authentication information.
func (d *Deluge) Login(password string) error {
func (d *Deluge) Login() error {
ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
defer cancel()

// This []string{config.Password} line is how you send auth creds. It's weird.
req, err := d.DelReq(AuthLogin, []string{password})
req, err := d.DelReq(ctx, AuthLogin, []string{d.Password})
if err != nil {
return fmt.Errorf("DelReq(AuthLogin, json): %v", err)
return fmt.Errorf("DelReq(AuthLogin, json): %w", err)
}

resp, err := d.Do(req)
if err != nil {
return fmt.Errorf("d.Do(req): %v", err)
return fmt.Errorf("d.Do(req): %w", err)
}
defer resp.Body.Close()

defer func() {
_, _ = io.Copy(ioutil.Discard, resp.Body)
_ = resp.Body.Close()
}()
_, _ = io.Copy(ioutil.Discard, resp.Body) // must read body to avoid memory leak.

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("authentication failed: %v[%v] (status: %v/%v)",
req.URL.String(), AuthLogin, resp.StatusCode, resp.Status)
return fmt.Errorf("%w: %v[%v] (status: %v/%v)",
ErrAuthFailed, req.URL.String(), AuthLogin, resp.StatusCode, resp.Status)
}

d.Client.cookie = true

return nil
}

// setVersion digs into the first server in the web UI to find the version.
// This is currently unused in this libyrar, and provided for display only.
func (d *Deluge) setVersion() error {
response, err := d.Get(GeHosts, []string{})
if err != nil {
Expand All @@ -114,17 +144,17 @@ func (d *Deluge) setVersion() error {
servers := make([][]interface{}, 0)
if err := json.Unmarshal(response.Result, &servers); err != nil {
d.logPayload(response.Result)
return fmt.Errorf("json.Unmarshal(rawResult1): %v", err)
return fmt.Errorf("json.Unmarshal(rawResult1): %w", err)
}

serverID := ""

// Store each server info (so consumers can access them easily).
for _, server := range servers {
serverID = server[0].(string)
serverID, _ = server[0].(string)
d.Backends[serverID] = Backend{
ID: serverID,
Addr: server[1].(string) + ":" + strconv.FormatFloat(server[2].(float64), 'f', 0, 64),
Addr: server[1].(string) + ":" + strconv.FormatFloat(server[2].(float64), 'f', 0, 64), //nolint:gomnd
Prot: server[3].(string),
}
}
Expand All @@ -138,30 +168,33 @@ func (d *Deluge) setVersion() error {
server := make([]interface{}, 0)
if err = json.Unmarshal(response.Result, &server); err != nil {
d.logPayload(response.Result)
return fmt.Errorf("json.Unmarshal(rawResult2): %v", err)
return fmt.Errorf("json.Unmarshal(rawResult2): %w", err)
}

const payloadSegments = 3

if len(server) < payloadSegments {
d.logPayload(response.Result)
return fmt.Errorf("invalid data returned while checking version")
return ErrInvalidVersion
}

// Version comes last in the mixed list.
d.Version = server[len(server)-1].(string)
var ok bool
if d.Version, ok = server[len(server)-1].(string); !ok {
return ErrInvalidVersion
}

return nil
}

// DelReq is a small helper function that adds headers and marshals the json.
func (d Deluge) DelReq(method string, params interface{}) (req *http.Request, err error) {
func (d Deluge) DelReq(ctx context.Context, method string, params interface{}) (req *http.Request, err error) {
d.id++

paramMap := map[string]interface{}{"method": method, "id": d.id, "params": params}
if data, errr := json.Marshal(paramMap); errr != nil {
return req, fmt.Errorf("json.Marshal(params): %v", err)
} else if req, err = http.NewRequest("POST", d.URL, bytes.NewBuffer(data)); err == nil {
return req, fmt.Errorf("json.Marshal(params): %w", err)
} else if req, err = http.NewRequestWithContext(ctx, "POST", d.URL, bytes.NewBuffer(data)); err == nil {
if d.auth != "" {
// In case Deluge is also behind HTTP auth.
req.Header.Add("Authorization", d.auth)
Expand All @@ -180,12 +213,12 @@ func (d Deluge) GetXfers() (map[string]*XferStatus, error) {

response, err := d.Get(GetAllTorrents, []string{"", ""})
if err != nil {
return xfers, fmt.Errorf("get(GetAllTorrents): %v", err)
return xfers, fmt.Errorf("get(GetAllTorrents): %w", err)
}

if err := json.Unmarshal(response.Result, &xfers); err != nil {
d.logPayload(response.Result)
return xfers, fmt.Errorf("json.Unmarshal(xfers): %v", err)
return xfers, fmt.Errorf("json.Unmarshal(xfers): %w", err)
}

return xfers, nil
Expand All @@ -200,47 +233,53 @@ func (d Deluge) GetXfersCompat() (map[string]*XferStatusCompat, error) {

response, err := d.Get(GetAllTorrents, []string{"", ""})
if err != nil {
return xfers, fmt.Errorf("get(GetAllTorrents): %v", err)
return xfers, fmt.Errorf("get(GetAllTorrents): %w", err)
}

if err := json.Unmarshal(response.Result, &xfers); err != nil {
d.logPayload(response.Result)
return xfers, fmt.Errorf("json.Unmarshal(xfers): %v", err)
return xfers, fmt.Errorf("json.Unmarshal(xfers): %w", err)
}

return xfers, nil
}

// Get a response from Deluge
// Get a response from Deluge.
func (d Deluge) Get(method string, params interface{}) (*Response, error) {
response := new(Response)

req, err := d.DelReq(method, params)
if !d.cookie {
if err := d.Login(); err != nil {
return response, err
}
}

ctx, cancel := context.WithTimeout(context.Background(), d.Timeout.Duration)
defer cancel()

req, err := d.DelReq(ctx, method, params)
if err != nil {
return response, fmt.Errorf("d.DelReq: %v", err)
return response, fmt.Errorf("d.DelReq: %w", err)
}

resp, err := d.Do(req)
if err != nil {
return response, fmt.Errorf("d.Do: %v", err)
return response, fmt.Errorf("d.Do: %w", err)
}

defer func() {
_ = resp.Body.Close()
}()
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return response, fmt.Errorf("ioutil.ReadAll: %v", err)
return response, fmt.Errorf("ioutil.ReadAll: %w", err)
}

if err = json.Unmarshal(body, &response); err != nil {
d.logPayload(response.Result)
return response, fmt.Errorf("json.Unmarshal(response): %v", err)
return response, fmt.Errorf("json.Unmarshal(response): %w", err)
}

if response.Error.Code != 0 {
return response, fmt.Errorf("deluge error: %v", response.Error.Message)
return response, fmt.Errorf("%w: %s", ErrDelugeError, response.Error.Message)
}

return response, nil
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module golift.io/deluge

go 1.13
go 1.17

require golang.org/x/net v0.0.0-20200202094626-16171245cfb2
require golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4
12 changes: 7 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0=
golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

0 comments on commit 88fcce2

Please sign in to comment.