feat(qbit): refactor url parse and add basic auth support (#245)

* feat(qbit): add basic auth and refactor url parse

* build: update dockerfile go base

* feat: only show port for legacy reasons
This commit is contained in:
Ludvig Lundgren 2022-05-20 17:17:00 +02:00 committed by GitHub
parent cf326a6c10
commit 62ada6de37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 301 additions and 8 deletions

View file

@ -10,6 +10,7 @@ import (
"net/http/cookiejar"
"net/url"
"os"
"path"
"strings"
"time"
@ -40,6 +41,13 @@ type Settings struct {
TLS bool
TLSSkipVerify bool
protocol string
BasicAuth bool
Basic Basic
}
type Basic struct {
Username string
Password string
}
func NewClient(s Settings) *Client {
@ -77,17 +85,21 @@ func NewClient(s Settings) *Client {
}
func (c *Client) get(endpoint string, opts map[string]string) (*http.Response, error) {
reqUrl := fmt.Sprintf("%v://%v:%v/api/v2/%v", c.settings.protocol, c.settings.Hostname, c.settings.Port, endpoint)
var err error
var resp *http.Response
reqUrl := buildUrl(c.settings, endpoint)
req, err := http.NewRequest("GET", reqUrl, nil)
if err != nil {
log.Error().Err(err).Msgf("GET: error %v", reqUrl)
return nil, err
}
if c.settings.BasicAuth {
req.SetBasicAuth(c.settings.Basic.Username, c.settings.Basic.Password)
}
// try request and if fail run 3 retries
for i, backoff := range backoffSchedule {
resp, err = c.http.Do(req)
@ -122,13 +134,18 @@ func (c *Client) post(endpoint string, opts map[string]string) (*http.Response,
var err error
var resp *http.Response
reqUrl := fmt.Sprintf("%v://%v:%v/api/v2/%v", c.settings.protocol, c.settings.Hostname, c.settings.Port, endpoint)
reqUrl := buildUrl(c.settings, endpoint)
req, err := http.NewRequest("POST", reqUrl, strings.NewReader(form.Encode()))
if err != nil {
log.Error().Err(err).Msgf("POST: req %v", reqUrl)
return nil, err
}
if c.settings.BasicAuth {
req.SetBasicAuth(c.settings.Basic.Username, c.settings.Basic.Password)
}
// add the content-type so qbittorrent knows what to expect
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
@ -154,6 +171,42 @@ func (c *Client) post(endpoint string, opts map[string]string) (*http.Response,
return resp, nil
}
func (c *Client) postBasic(endpoint string, opts map[string]string) (*http.Response, error) {
// add optional parameters that the user wants
form := url.Values{}
if opts != nil {
for k, v := range opts {
form.Add(k, v)
}
}
var err error
var resp *http.Response
reqUrl := buildUrl(c.settings, endpoint)
req, err := http.NewRequest("POST", reqUrl, strings.NewReader(form.Encode()))
if err != nil {
log.Error().Err(err).Msgf("POST: req %v", reqUrl)
return nil, err
}
if c.settings.BasicAuth {
req.SetBasicAuth(c.settings.Basic.Username, c.settings.Basic.Password)
}
// add the content-type so qbittorrent knows what to expect
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err = c.http.Do(req)
if err != nil {
log.Error().Err(err).Msgf("POST: do %v", reqUrl)
return nil, err
}
return resp, nil
}
func (c *Client) postFile(endpoint string, fileName string, opts map[string]string) (*http.Response, error) {
var err error
var resp *http.Response
@ -206,13 +259,17 @@ func (c *Client) postFile(endpoint string, fileName string, opts map[string]stri
// Close multipart writer
multiPartWriter.Close()
reqUrl := fmt.Sprintf("%v://%v:%v/api/v2/%v", c.settings.protocol, c.settings.Hostname, c.settings.Port, endpoint)
reqUrl := buildUrl(c.settings, endpoint)
req, err := http.NewRequest("POST", reqUrl, &requestBody)
if err != nil {
log.Error().Err(err).Msgf("POST file: could not create request object %v", fileName)
return nil, err
}
if c.settings.BasicAuth {
req.SetBasicAuth(c.settings.Basic.Username, c.settings.Basic.Password)
}
// Set correct content type
req.Header.Set("Content-Type", multiPartWriter.FormDataContentType())
@ -242,3 +299,50 @@ func (c *Client) setCookies(cookies []*http.Cookie) {
cookieURL, _ := url.Parse(fmt.Sprintf("%v://%v:%v", c.settings.protocol, c.settings.Hostname, c.settings.Port))
c.http.Jar.SetCookies(cookieURL, cookies)
}
func buildUrl(settings Settings, endpoint string) string {
// parse url
u, _ := url.Parse(settings.Hostname)
// reset Opaque
u.Opaque = ""
// set scheme
scheme := "http"
if u.Scheme == "http" || u.Scheme == "https" {
if settings.TLS {
scheme = "https"
}
u.Scheme = scheme
} else {
if settings.TLS {
scheme = "https"
}
u.Scheme = scheme
}
// if host is empty lets use one from settings
if u.Host == "" {
u.Host = settings.Hostname
}
// reset Path
if u.Host == u.Path {
u.Path = ""
}
// handle ports
if settings.Port > 0 {
if settings.Port == 80 || settings.Port == 443 {
// skip for regular http and https
} else {
u.Host = fmt.Sprintf("%v:%v", u.Host, settings.Port)
}
}
// join path
u.Path = path.Join(u.Path, "/api/v2/", endpoint)
// make into new string and return
return u.String()
}

View file

@ -0,0 +1,135 @@
package qbittorrent
import "testing"
func Test_buildUrl(t *testing.T) {
type args struct {
settings Settings
endpoint string
}
tests := []struct {
name string
args args
want string
}{
{
name: "build_url_1",
args: args{
settings: Settings{
Hostname: "https://qbit.domain.ltd",
Port: 0,
Username: "",
Password: "",
TLS: true,
TLSSkipVerify: false,
protocol: "",
},
endpoint: "auth/login",
},
want: "https://qbit.domain.ltd/api/v2/auth/login",
},
{
name: "build_url_2",
args: args{
settings: Settings{
Hostname: "http://qbit.domain.ltd",
Port: 0,
Username: "",
Password: "",
TLS: false,
TLSSkipVerify: false,
protocol: "",
},
endpoint: "/auth/login",
},
want: "http://qbit.domain.ltd/api/v2/auth/login",
},
{
name: "build_url_3",
args: args{
settings: Settings{
Hostname: "https://qbit.domain.ltd:8080",
Port: 0,
Username: "",
Password: "",
TLS: true,
TLSSkipVerify: false,
protocol: "",
},
endpoint: "/auth/login",
},
want: "https://qbit.domain.ltd:8080/api/v2/auth/login",
},
{
name: "build_url_4",
args: args{
settings: Settings{
Hostname: "qbit.domain.ltd:8080",
Port: 0,
Username: "",
Password: "",
TLS: false,
TLSSkipVerify: false,
protocol: "",
},
endpoint: "/auth/login",
},
want: "http://qbit.domain.ltd:8080/api/v2/auth/login",
},
{
name: "build_url_5",
args: args{
settings: Settings{
Hostname: "qbit.domain.ltd",
Port: 8080,
Username: "",
Password: "",
TLS: false,
TLSSkipVerify: false,
protocol: "",
},
endpoint: "/auth/login",
},
want: "http://qbit.domain.ltd:8080/api/v2/auth/login",
},
{
name: "build_url_6",
args: args{
settings: Settings{
Hostname: "qbit.domain.ltd",
Port: 443,
Username: "",
Password: "",
TLS: true,
TLSSkipVerify: false,
protocol: "",
},
endpoint: "/auth/login",
},
want: "https://qbit.domain.ltd/api/v2/auth/login",
},
{
name: "build_url_6",
args: args{
settings: Settings{
Hostname: "qbit.domain.ltd",
Port: 10200,
Username: "",
Password: "",
TLS: false,
TLSSkipVerify: false,
protocol: "",
},
endpoint: "/auth/login",
},
want: "http://qbit.domain.ltd:10200/api/v2/auth/login",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildUrl(tt.args.settings, tt.args.endpoint); got != tt.want {
t.Errorf("buildUrl() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -18,7 +18,7 @@ func (c *Client) Login() error {
credentials["username"] = c.settings.Username
credentials["password"] = c.settings.Password
resp, err := c.post("auth/login", credentials)
resp, err := c.postBasic("auth/login", credentials)
if err != nil {
log.Error().Err(err).Msg("login error")
return err