@@ -2,6 +2,7 @@ package deluge
2
2
3
3
import (
4
4
"bytes"
5
+ "context"
5
6
"crypto/tls"
6
7
"encoding/base64"
7
8
"encoding/json"
@@ -17,24 +18,45 @@ import (
17
18
"golang.org/x/net/publicsuffix"
18
19
)
19
20
21
+ // Custom errors.
22
+ var (
23
+ ErrInvalidVersion = fmt .Errorf ("invalid data returned while checking version" )
24
+ ErrDelugeError = fmt .Errorf ("deluge error" )
25
+ ErrAuthFailed = fmt .Errorf ("authentication failed" )
26
+ )
27
+
28
+ type Client struct {
29
+ * http.Client
30
+ cookie bool
31
+ }
32
+
20
33
// Deluge is what you get for providing a password.
21
34
type Deluge struct {
22
- * http. Client
23
- URL string
35
+ * Client
36
+ * Config
24
37
auth string
25
38
id int
26
39
Version string // Currently unused, for display purposes only.
27
40
Backends map [string ]Backend // Currently unused, for display purposes only.
28
41
DebugLog func (msg string , fmt ... interface {})
29
42
}
30
43
44
+ // NewNoAuth returns a Deluge object without authenticating or trying to connect.
45
+ func NewNoAuth (config * Config ) (* Deluge , error ) {
46
+ return newConfig (config , false )
47
+ }
48
+
31
49
// New creates a http.Client with authenticated cookies.
32
50
// Used to make additional, authenticated requests to the APIs.
33
51
func New (config * Config ) (* Deluge , error ) {
52
+ return newConfig (config , true )
53
+ }
54
+
55
+ func newConfig (config * Config , login bool ) (* Deluge , error ) {
34
56
// The cookie jar is used to auth Deluge.
35
57
jar , err := cookiejar .New (& cookiejar.Options {PublicSuffixList : publicsuffix .List })
36
58
if err != nil {
37
- return nil , fmt .Errorf ("cookiejar.New(publicsuffix): %v " , err )
59
+ return nil , fmt .Errorf ("cookiejar.New(publicsuffix): %w " , err )
38
60
}
39
61
40
62
if ! strings .HasSuffix (config .URL , "/" ) {
@@ -51,18 +73,24 @@ func New(config *Config) (*Deluge, error) {
51
73
}
52
74
53
75
deluge := & Deluge {
54
- URL : config . URL ,
76
+ Config : config ,
55
77
auth : config .HTTPUser ,
56
78
Backends : make (map [string ]Backend ),
57
79
DebugLog : config .DebugLog ,
58
- Client : & http.Client {
59
- Transport : & http.Transport {TLSClientConfig : & tls.Config {InsecureSkipVerify : config .VerifySSL }},
60
- Jar : jar ,
61
- Timeout : config .Timeout .Round (time .Millisecond ),
80
+ Client : & Client {
81
+ Client : & http.Client {
82
+ Transport : & http.Transport {TLSClientConfig : & tls.Config {InsecureSkipVerify : config .VerifySSL }}, //nolint:gosec
83
+ Jar : jar ,
84
+ Timeout : config .Timeout .Round (time .Millisecond ),
85
+ },
62
86
},
63
87
}
64
88
65
- if err := deluge .Login (config .Password ); err != nil {
89
+ if ! login {
90
+ return deluge , nil
91
+ }
92
+
93
+ if err := deluge .Login (); err != nil {
66
94
return deluge , err
67
95
}
68
96
@@ -76,33 +104,35 @@ func New(config *Config) (*Deluge, error) {
76
104
}
77
105
78
106
// Login sets the cookie jar with authentication information.
79
- func (d * Deluge ) Login (password string ) error {
107
+ func (d * Deluge ) Login () error {
108
+ ctx , cancel := context .WithTimeout (context .Background (), d .Timeout .Duration )
109
+ defer cancel ()
110
+
80
111
// This []string{config.Password} line is how you send auth creds. It's weird.
81
- req , err := d .DelReq (AuthLogin , []string {password })
112
+ req , err := d .DelReq (ctx , AuthLogin , []string {d . Password })
82
113
if err != nil {
83
- return fmt .Errorf ("DelReq(AuthLogin, json): %v " , err )
114
+ return fmt .Errorf ("DelReq(AuthLogin, json): %w " , err )
84
115
}
85
116
86
117
resp , err := d .Do (req )
87
118
if err != nil {
88
- return fmt .Errorf ("d.Do(req): %v " , err )
119
+ return fmt .Errorf ("d.Do(req): %w " , err )
89
120
}
121
+ defer resp .Body .Close ()
90
122
91
- defer func () {
92
- _ , _ = io .Copy (ioutil .Discard , resp .Body )
93
- _ = resp .Body .Close ()
94
- }()
123
+ _ , _ = io .Copy (ioutil .Discard , resp .Body ) // must read body to avoid memory leak.
95
124
96
125
if resp .StatusCode != http .StatusOK {
97
- return fmt .Errorf ("authentication failed : %v[%v] (status: %v/%v)" ,
98
- req .URL .String (), AuthLogin , resp .StatusCode , resp .Status )
126
+ return fmt .Errorf ("%w : %v[%v] (status: %v/%v)" ,
127
+ ErrAuthFailed , req .URL .String (), AuthLogin , resp .StatusCode , resp .Status )
99
128
}
100
129
130
+ d .Client .cookie = true
131
+
101
132
return nil
102
133
}
103
134
104
135
// setVersion digs into the first server in the web UI to find the version.
105
- // This is currently unused in this libyrar, and provided for display only.
106
136
func (d * Deluge ) setVersion () error {
107
137
response , err := d .Get (GeHosts , []string {})
108
138
if err != nil {
@@ -114,17 +144,17 @@ func (d *Deluge) setVersion() error {
114
144
servers := make ([][]interface {}, 0 )
115
145
if err := json .Unmarshal (response .Result , & servers ); err != nil {
116
146
d .logPayload (response .Result )
117
- return fmt .Errorf ("json.Unmarshal(rawResult1): %v " , err )
147
+ return fmt .Errorf ("json.Unmarshal(rawResult1): %w " , err )
118
148
}
119
149
120
150
serverID := ""
121
151
122
152
// Store each server info (so consumers can access them easily).
123
153
for _ , server := range servers {
124
- serverID = server [0 ].(string )
154
+ serverID , _ = server [0 ].(string )
125
155
d .Backends [serverID ] = Backend {
126
156
ID : serverID ,
127
- Addr : server [1 ].(string ) + ":" + strconv .FormatFloat (server [2 ].(float64 ), 'f' , 0 , 64 ),
157
+ Addr : server [1 ].(string ) + ":" + strconv .FormatFloat (server [2 ].(float64 ), 'f' , 0 , 64 ), //nolint:gomnd
128
158
Prot : server [3 ].(string ),
129
159
}
130
160
}
@@ -138,30 +168,33 @@ func (d *Deluge) setVersion() error {
138
168
server := make ([]interface {}, 0 )
139
169
if err = json .Unmarshal (response .Result , & server ); err != nil {
140
170
d .logPayload (response .Result )
141
- return fmt .Errorf ("json.Unmarshal(rawResult2): %v " , err )
171
+ return fmt .Errorf ("json.Unmarshal(rawResult2): %w " , err )
142
172
}
143
173
144
174
const payloadSegments = 3
145
175
146
176
if len (server ) < payloadSegments {
147
177
d .logPayload (response .Result )
148
- return fmt . Errorf ( "invalid data returned while checking version" )
178
+ return ErrInvalidVersion
149
179
}
150
180
151
181
// Version comes last in the mixed list.
152
- d .Version = server [len (server )- 1 ].(string )
182
+ var ok bool
183
+ if d .Version , ok = server [len (server )- 1 ].(string ); ! ok {
184
+ return ErrInvalidVersion
185
+ }
153
186
154
187
return nil
155
188
}
156
189
157
190
// DelReq is a small helper function that adds headers and marshals the json.
158
- func (d Deluge ) DelReq (method string , params interface {}) (req * http.Request , err error ) {
191
+ func (d Deluge ) DelReq (ctx context. Context , method string , params interface {}) (req * http.Request , err error ) {
159
192
d .id ++
160
193
161
194
paramMap := map [string ]interface {}{"method" : method , "id" : d .id , "params" : params }
162
195
if data , errr := json .Marshal (paramMap ); errr != nil {
163
- return req , fmt .Errorf ("json.Marshal(params): %v " , err )
164
- } else if req , err = http .NewRequest ( "POST" , d .URL , bytes .NewBuffer (data )); err == nil {
196
+ return req , fmt .Errorf ("json.Marshal(params): %w " , err )
197
+ } else if req , err = http .NewRequestWithContext ( ctx , "POST" , d .URL , bytes .NewBuffer (data )); err == nil {
165
198
if d .auth != "" {
166
199
// In case Deluge is also behind HTTP auth.
167
200
req .Header .Add ("Authorization" , d .auth )
@@ -180,12 +213,12 @@ func (d Deluge) GetXfers() (map[string]*XferStatus, error) {
180
213
181
214
response , err := d .Get (GetAllTorrents , []string {"" , "" })
182
215
if err != nil {
183
- return xfers , fmt .Errorf ("get(GetAllTorrents): %v " , err )
216
+ return xfers , fmt .Errorf ("get(GetAllTorrents): %w " , err )
184
217
}
185
218
186
219
if err := json .Unmarshal (response .Result , & xfers ); err != nil {
187
220
d .logPayload (response .Result )
188
- return xfers , fmt .Errorf ("json.Unmarshal(xfers): %v " , err )
221
+ return xfers , fmt .Errorf ("json.Unmarshal(xfers): %w " , err )
189
222
}
190
223
191
224
return xfers , nil
@@ -200,47 +233,53 @@ func (d Deluge) GetXfersCompat() (map[string]*XferStatusCompat, error) {
200
233
201
234
response , err := d .Get (GetAllTorrents , []string {"" , "" })
202
235
if err != nil {
203
- return xfers , fmt .Errorf ("get(GetAllTorrents): %v " , err )
236
+ return xfers , fmt .Errorf ("get(GetAllTorrents): %w " , err )
204
237
}
205
238
206
239
if err := json .Unmarshal (response .Result , & xfers ); err != nil {
207
240
d .logPayload (response .Result )
208
- return xfers , fmt .Errorf ("json.Unmarshal(xfers): %v " , err )
241
+ return xfers , fmt .Errorf ("json.Unmarshal(xfers): %w " , err )
209
242
}
210
243
211
244
return xfers , nil
212
245
}
213
246
214
- // Get a response from Deluge
247
+ // Get a response from Deluge.
215
248
func (d Deluge ) Get (method string , params interface {}) (* Response , error ) {
216
249
response := new (Response )
217
250
218
- req , err := d .DelReq (method , params )
251
+ if ! d .cookie {
252
+ if err := d .Login (); err != nil {
253
+ return response , err
254
+ }
255
+ }
256
+
257
+ ctx , cancel := context .WithTimeout (context .Background (), d .Timeout .Duration )
258
+ defer cancel ()
259
+
260
+ req , err := d .DelReq (ctx , method , params )
219
261
if err != nil {
220
- return response , fmt .Errorf ("d.DelReq: %v " , err )
262
+ return response , fmt .Errorf ("d.DelReq: %w " , err )
221
263
}
222
264
223
265
resp , err := d .Do (req )
224
266
if err != nil {
225
- return response , fmt .Errorf ("d.Do: %v " , err )
267
+ return response , fmt .Errorf ("d.Do: %w " , err )
226
268
}
227
-
228
- defer func () {
229
- _ = resp .Body .Close ()
230
- }()
269
+ defer resp .Body .Close ()
231
270
232
271
body , err := ioutil .ReadAll (resp .Body )
233
272
if err != nil {
234
- return response , fmt .Errorf ("ioutil.ReadAll: %v " , err )
273
+ return response , fmt .Errorf ("ioutil.ReadAll: %w " , err )
235
274
}
236
275
237
276
if err = json .Unmarshal (body , & response ); err != nil {
238
277
d .logPayload (response .Result )
239
- return response , fmt .Errorf ("json.Unmarshal(response): %v " , err )
278
+ return response , fmt .Errorf ("json.Unmarshal(response): %w " , err )
240
279
}
241
280
242
281
if response .Error .Code != 0 {
243
- return response , fmt .Errorf ("deluge error : %v" , response .Error .Message )
282
+ return response , fmt .Errorf ("%w : %s" , ErrDelugeError , response .Error .Message )
244
283
}
245
284
246
285
return response , nil
0 commit comments