14 type HTTPClient struct {
15 httpClient http.Client
25 type HTTPClientConfig struct {
27 HeaderAPIKeyName string
29 HeaderClientKeyName string
33 // Inspired by syncthing/cmd/cli
35 const insecure = false
37 // HTTPNewClient creates a new HTTP client to deal with Syncthing
38 func HTTPNewClient(baseURL string, cfg HTTPClientConfig) (*HTTPClient, error) {
40 // Create w new Http client
41 httpClient := http.Client{
42 Transport: &http.Transport{
43 TLSClientConfig: &tls.Config{
44 InsecureSkipVerify: insecure,
49 httpClient: httpClient,
53 /* TODO - add user + pwd support
54 username: c.GlobalString("username"),
55 password: c.GlobalString("password"),
59 if client.apikey == "" {
60 if err := client.getCidAndCsrf(); err != nil {
67 // Send request to retrieve Client id and/or CSRF token
68 func (c *HTTPClient) getCidAndCsrf() error {
69 request, err := http.NewRequest("GET", c.endpoint, nil)
73 if _, err := c.handleRequest(request); err != nil {
77 return errors.New("Failed to get device ID")
79 if !c.conf.CsrfDisable && c.csrf == "" {
80 return errors.New("Failed to get CSRF token")
85 // GetClientID returns the id
86 func (c *HTTPClient) GetClientID() string {
90 // formatURL Build full url by concatenating all parts
91 func (c *HTTPClient) formatURL(endURL string) string {
93 if !strings.HasSuffix(url, "/") {
96 url += strings.TrimLeft(c.conf.URLPrefix, "/")
97 if !strings.HasSuffix(url, "/") {
100 return url + strings.TrimLeft(endURL, "/")
103 // HTTPGet Send a Get request to client and return an error object
104 func (c *HTTPClient) HTTPGet(url string, data *[]byte) error {
105 _, err := c.HTTPGetWithRes(url, data)
109 // HTTPGetWithRes Send a Get request to client and return both response and error
110 func (c *HTTPClient) HTTPGetWithRes(url string, data *[]byte) (*http.Response, error) {
111 request, err := http.NewRequest("GET", c.formatURL(url), nil)
115 res, err := c.handleRequest(request)
119 if res.StatusCode != 200 {
120 return res, errors.New(res.Status)
123 *data = c.responseToBArray(res)
128 // HTTPPost Send a POST request to client and return an error object
129 func (c *HTTPClient) HTTPPost(url string, body string) error {
130 _, err := c.HTTPPostWithRes(url, body)
134 // HTTPPostWithRes Send a POST request to client and return both response and error
135 func (c *HTTPClient) HTTPPostWithRes(url string, body string) (*http.Response, error) {
136 request, err := http.NewRequest("POST", c.formatURL(url), bytes.NewBufferString(body))
140 res, err := c.handleRequest(request)
144 if res.StatusCode != 200 {
145 return res, errors.New(res.Status)
150 func (c *HTTPClient) responseToBArray(response *http.Response) []byte {
151 defer response.Body.Close()
152 bytes, err := ioutil.ReadAll(response.Body)
154 // TODO improved error reporting
155 fmt.Println("ERROR: " + err.Error())
160 func (c *HTTPClient) handleRequest(request *http.Request) (*http.Response, error) {
161 if c.conf.HeaderAPIKeyName != "" && c.apikey != "" {
162 request.Header.Set(c.conf.HeaderAPIKeyName, c.apikey)
164 if c.conf.HeaderClientKeyName != "" && c.id != "" {
165 request.Header.Set(c.conf.HeaderClientKeyName, c.id)
167 if c.username != "" || c.password != "" {
168 request.SetBasicAuth(c.username, c.password)
171 request.Header.Set("X-CSRF-Token-"+c.id[:5], c.csrf)
174 response, err := c.httpClient.Do(request)
179 // Detect client ID change
180 cid := response.Header.Get(c.conf.HeaderClientKeyName)
181 if cid != "" && c.id != cid {
185 // Detect CSR token change
186 for _, item := range response.Cookies() {
187 if item.Name == "CSRF-Token-"+c.id[:5] {
195 if response.StatusCode == 404 {
196 return nil, errors.New("Invalid endpoint or API call")
197 } else if response.StatusCode == 401 {
198 return nil, errors.New("Invalid username or password")
199 } else if response.StatusCode == 403 {
201 // Request a new Csrf for next requests
203 return nil, errors.New("Invalid CSRF token")
205 return nil, errors.New("Invalid API key")
206 } else if response.StatusCode != 200 {
207 data := make(map[string]interface{})
208 // Try to decode error field of APIError struct
209 json.Unmarshal(c.responseToBArray(response), &data)
210 if err, found := data["error"]; found {
211 return nil, fmt.Errorf(err.(string))
213 body := strings.TrimSpace(string(c.responseToBArray(response)))
215 return nil, fmt.Errorf(body)
218 return nil, errors.New("Unknown HTTP status returned: " + response.Status)