2019-04-25 19:06:53 +02:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2020-02-07 14:53:15 +01:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2019-04-25 19:06:53 +02:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gitea
import (
2020-09-16 04:01:41 +02:00
"context"
2019-04-25 19:06:53 +02:00
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
2020-02-07 14:53:15 +01:00
"sync"
"github.com/hashicorp/go-version"
2019-04-25 19:06:53 +02:00
)
2019-10-13 17:13:33 +02:00
var jsonHeader = http . Header { "content-type" : [ ] string { "application/json" } }
2019-04-25 19:06:53 +02:00
// Version return the library version
func Version ( ) string {
2020-09-16 04:01:41 +02:00
return "0.13.0"
2019-04-25 19:06:53 +02:00
}
2020-02-07 14:53:15 +01:00
// Client represents a Gitea API client.
2019-04-25 19:06:53 +02:00
type Client struct {
2020-02-07 14:53:15 +01:00
url string
accessToken string
username string
password string
2020-05-27 15:48:09 +02:00
otp string
2020-02-07 14:53:15 +01:00
sudo string
client * http . Client
2020-09-16 04:01:41 +02:00
ctx context . Context
2020-02-07 14:53:15 +01:00
serverVersion * version . Version
versionLock sync . RWMutex
2019-04-25 19:06:53 +02:00
}
2020-09-16 04:01:41 +02:00
// Response represents the gitea response
type Response struct {
* http . Response
}
2019-04-25 19:06:53 +02:00
// NewClient initializes and returns a API client.
2020-09-16 04:01:41 +02:00
func NewClient ( url string , options ... func ( * Client ) ) ( * Client , error ) {
client := & Client {
url : strings . TrimSuffix ( url , "/" ) ,
client : & http . Client { } ,
ctx : context . Background ( ) ,
}
for _ , opt := range options {
opt ( client )
}
if err := client . CheckServerVersionConstraint ( ">=1.10" ) ; err != nil {
return nil , err
2019-04-25 19:06:53 +02:00
}
2020-09-16 04:01:41 +02:00
return client , nil
2019-04-25 19:06:53 +02:00
}
// NewClientWithHTTP creates an API client with a custom http client
2020-09-16 04:01:41 +02:00
// Deprecated use SetHTTPClient option
2020-01-04 02:44:33 +01:00
func NewClientWithHTTP ( url string , httpClient * http . Client ) * Client {
2020-09-16 04:01:41 +02:00
client , _ := NewClient ( url , SetHTTPClient ( httpClient ) )
2020-01-04 02:44:33 +01:00
return client
2019-04-25 19:06:53 +02:00
}
2020-09-16 04:01:41 +02:00
// SetHTTPClient is an option for NewClient to set custom http client
func SetHTTPClient ( httpClient * http . Client ) func ( client * Client ) {
return func ( client * Client ) {
client . client = httpClient
}
}
// SetToken is an option for NewClient to set token
func SetToken ( token string ) func ( client * Client ) {
return func ( client * Client ) {
client . accessToken = token
}
}
// SetBasicAuth is an option for NewClient to set username and password
func SetBasicAuth ( username , password string ) func ( client * Client ) {
return func ( client * Client ) {
client . SetBasicAuth ( username , password )
}
}
// SetBasicAuth sets username and password
2020-02-07 14:53:15 +01:00
func ( c * Client ) SetBasicAuth ( username , password string ) {
c . username , c . password = username , password
}
2020-09-16 04:01:41 +02:00
// SetOTP is an option for NewClient to set OTP for 2FA
func SetOTP ( otp string ) func ( client * Client ) {
return func ( client * Client ) {
client . SetOTP ( otp )
}
}
2020-05-27 15:48:09 +02:00
// SetOTP sets OTP for 2FA
func ( c * Client ) SetOTP ( otp string ) {
c . otp = otp
}
2020-09-16 04:01:41 +02:00
// SetContext is an option for NewClient to set context
func SetContext ( ctx context . Context ) func ( client * Client ) {
return func ( client * Client ) {
client . SetContext ( ctx )
}
}
// SetContext set context witch is used for http requests
func ( c * Client ) SetContext ( ctx context . Context ) {
c . ctx = ctx
}
2019-04-25 19:06:53 +02:00
// SetHTTPClient replaces default http.Client with user given one.
func ( c * Client ) SetHTTPClient ( client * http . Client ) {
c . client = client
}
2020-09-16 04:01:41 +02:00
// SetSudo is an option for NewClient to set sudo header
func SetSudo ( sudo string ) func ( client * Client ) {
return func ( client * Client ) {
client . SetSudo ( sudo )
}
}
2019-04-25 19:06:53 +02:00
// SetSudo sets username to impersonate.
func ( c * Client ) SetSudo ( sudo string ) {
c . sudo = sudo
}
2020-09-16 04:01:41 +02:00
func ( c * Client ) getWebResponse ( method , path string , body io . Reader ) ( [ ] byte , * Response , error ) {
req , err := http . NewRequestWithContext ( c . ctx , method , c . url + path , body )
if err != nil {
return nil , nil , err
}
resp , err := c . client . Do ( req )
if err != nil {
return nil , nil , err
}
defer resp . Body . Close ( )
data , err := ioutil . ReadAll ( resp . Body )
return data , & Response { resp } , nil
}
func ( c * Client ) doRequest ( method , path string , header http . Header , body io . Reader ) ( * Response , error ) {
req , err := http . NewRequestWithContext ( c . ctx , method , c . url + "/api/v1" + path , body )
2019-04-25 19:06:53 +02:00
if err != nil {
return nil , err
}
if len ( c . accessToken ) != 0 {
req . Header . Set ( "Authorization" , "token " + c . accessToken )
}
2020-05-27 15:48:09 +02:00
if len ( c . otp ) != 0 {
req . Header . Set ( "X-GITEA-OTP" , c . otp )
}
2020-02-07 14:53:15 +01:00
if len ( c . username ) != 0 {
req . SetBasicAuth ( c . username , c . password )
}
2020-05-27 15:48:09 +02:00
if len ( c . sudo ) != 0 {
2019-04-25 19:06:53 +02:00
req . Header . Set ( "Sudo" , c . sudo )
}
for k , v := range header {
req . Header [ k ] = v
}
2020-09-16 04:01:41 +02:00
resp , err := c . client . Do ( req )
if err != nil {
return nil , err
}
return & Response { resp } , nil
2019-04-25 19:06:53 +02:00
}
2020-09-16 04:01:41 +02:00
func ( c * Client ) getResponse ( method , path string , header http . Header , body io . Reader ) ( [ ] byte , * Response , error ) {
2019-04-25 19:06:53 +02:00
resp , err := c . doRequest ( method , path , header , body )
if err != nil {
2020-09-16 04:01:41 +02:00
return nil , nil , err
2019-04-25 19:06:53 +02:00
}
defer resp . Body . Close ( )
data , err := ioutil . ReadAll ( resp . Body )
if err != nil {
2020-09-16 04:01:41 +02:00
return nil , resp , err
2019-04-25 19:06:53 +02:00
}
switch resp . StatusCode {
case 403 :
2020-09-16 04:01:41 +02:00
return data , resp , errors . New ( "403 Forbidden" )
2019-04-25 19:06:53 +02:00
case 404 :
2020-09-16 04:01:41 +02:00
return data , resp , errors . New ( "404 Not Found" )
2019-04-25 19:06:53 +02:00
case 409 :
2020-09-16 04:01:41 +02:00
return data , resp , errors . New ( "409 Conflict" )
2019-04-25 19:06:53 +02:00
case 422 :
2020-09-16 04:01:41 +02:00
return data , resp , fmt . Errorf ( "422 Unprocessable Entity: %s" , string ( data ) )
2019-04-25 19:06:53 +02:00
}
if resp . StatusCode / 100 != 2 {
errMap := make ( map [ string ] interface { } )
if err = json . Unmarshal ( data , & errMap ) ; err != nil {
// when the JSON can't be parsed, data was probably empty or a plain string,
// so we try to return a helpful error anyway
2020-09-16 04:01:41 +02:00
return data , resp , fmt . Errorf ( "Unknown API Error: %d\nRequest: '%s' with '%s' method '%s' header and '%s' body" , resp . StatusCode , path , method , header , string ( data ) )
2019-04-25 19:06:53 +02:00
}
2020-09-16 04:01:41 +02:00
return data , resp , errors . New ( errMap [ "message" ] . ( string ) )
2019-04-25 19:06:53 +02:00
}
2020-09-16 04:01:41 +02:00
return data , resp , nil
2019-04-25 19:06:53 +02:00
}
2020-09-16 04:01:41 +02:00
func ( c * Client ) getParsedResponse ( method , path string , header http . Header , body io . Reader , obj interface { } ) ( * Response , error ) {
data , resp , err := c . getResponse ( method , path , header , body )
2019-04-25 19:06:53 +02:00
if err != nil {
2020-09-16 04:01:41 +02:00
return nil , err
2019-04-25 19:06:53 +02:00
}
2020-09-16 04:01:41 +02:00
return resp , json . Unmarshal ( data , obj )
2019-04-25 19:06:53 +02:00
}
2020-09-16 04:01:41 +02:00
func ( c * Client ) getStatusCode ( method , path string , header http . Header , body io . Reader ) ( int , * Response , error ) {
2019-04-25 19:06:53 +02:00
resp , err := c . doRequest ( method , path , header , body )
if err != nil {
2020-09-16 04:01:41 +02:00
return - 1 , resp , err
2019-04-25 19:06:53 +02:00
}
defer resp . Body . Close ( )
2020-09-16 04:01:41 +02:00
return resp . StatusCode , resp , nil
2019-04-25 19:06:53 +02:00
}