github.com/mundipagg/boleto-api@v0.0.0-20230620145841-3f9ec742599f/util/http.go (about)

     1  package util
     2  
     3  import (
     4  	"context"
     5  	"crypto"
     6  	"crypto/tls"
     7  	"crypto/x509"
     8  	"encoding/base64"
     9  	"encoding/pem"
    10  	"errors"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"net"
    14  	"net/http"
    15  	"net/url"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    19  	"time"
    20  
    21  	"github.com/mundipagg/boleto-api/certificate"
    22  	"github.com/mundipagg/boleto-api/log"
    23  
    24  	s "github.com/fullsailor/pkcs7"
    25  	"github.com/mundipagg/boleto-api/config"
    26  )
    27  
    28  var defaultDialer = &net.Dialer{Timeout: 16 * time.Second, KeepAlive: 16 * time.Second}
    29  
    30  var (
    31  	client            *http.Client
    32  	onceDefaultClient = &sync.Once{}
    33  	onceTransport     = &sync.Once{}
    34  	icpCert           certificate.ICPCertificate
    35  	transport         *http.Transport
    36  )
    37  
    38  // HTTPInterface is an abstraction for HTTP client
    39  type HTTPInterface interface {
    40  	Post(url string, headers map[string]string, body interface{}) (*http.Response, error)
    41  }
    42  
    43  // HTTPClient is the struct for making requests
    44  type HTTPClient struct{}
    45  
    46  // PostFormEncoded is a function for making requests using Post Http method with content-type application/x-www-form-urlencoded.
    47  //
    48  // It receives an endpoint, params and pointer for log and it creates a new Post request, returning []byte and a error.
    49  func (hc *HTTPClient) PostFormURLEncoded(endpoint string, params map[string]string, log *log.Log) ([]byte, error) {
    50  	client := &http.Client{
    51  		Timeout: time.Second * 10,
    52  	}
    53  
    54  	uri, err := url.ParseRequestURI(endpoint)
    55  	if err != nil {
    56  		return []byte(""), err
    57  	}
    58  
    59  	values := uri.Query()
    60  	for k, v := range params {
    61  		values.Set(k, v)
    62  	}
    63  
    64  	req, err := http.NewRequest(http.MethodPost, uri.String(), strings.NewReader(values.Encode())) // URL-encoded payload
    65  
    66  	if err != nil {
    67  		return []byte(""), err
    68  	}
    69  
    70  	header := map[string]string{
    71  		"content-type":   "application/x-www-form-urlencoded",
    72  		"content-length": strconv.Itoa(len(values.Encode())),
    73  	}
    74  
    75  	for k, v := range header {
    76  		req.Header.Add(k, v)
    77  	}
    78  
    79  	log.Request(params, endpoint, header)
    80  	resp, err := client.Do(req)
    81  	if err != nil {
    82  		return []byte(""), err
    83  	}
    84  	defer resp.Body.Close()
    85  
    86  	if resp.StatusCode != http.StatusOK {
    87  		return []byte(""), fmt.Errorf("stone authentication returns status code %d", resp.StatusCode)
    88  	}
    89  
    90  	respByte, err := ioutil.ReadAll(resp.Body)
    91  
    92  	respBody := string(respByte)
    93  
    94  	mask := NewMask(log, "Response")
    95  
    96  	log.Response(mask.MaskJsonContentFields(respBody), endpoint, nil)
    97  
    98  	return respByte, err
    99  }
   100  
   101  // DefaultHTTPClient retorna um cliente http configurado para dar um skip na validação do certificado digital
   102  func DefaultHTTPClient() *http.Client {
   103  	onceDefaultClient.Do(func() {
   104  		client = &http.Client{
   105  			Transport: &http.Transport{
   106  				Dial:                defaultDialer.Dial,
   107  				TLSHandshakeTimeout: 16 * time.Second,
   108  				TLSClientConfig: &tls.Config{
   109  					InsecureSkipVerify: true,
   110  				},
   111  			},
   112  		}
   113  	})
   114  	return client
   115  }
   116  
   117  //Post faz um requisição POST para uma URL e retorna o response, status e erro
   118  func PostReponseWithHeader(url, body, timeout string, header map[string]string) (string, string, int, error) {
   119  	return doRequest("POST", url, body, timeout, header)
   120  }
   121  
   122  //Post faz um requisição POST para uma URL e retorna o response, status e erro
   123  func Post(url, body, timeout string, header map[string]string) (string, int, error) {
   124  	resp, _, st, err := doRequest("POST", url, body, timeout, header)
   125  	return resp, st, err
   126  }
   127  
   128  //PostWithHeader faz um requisição POST para uma URL e retorna o response, status e erro
   129  func PostWithHeader(url, body, timeout string, header map[string]string) (string, map[string]interface{}, int, error) {
   130  	resp, respHeader, st, err := doRequestWithHeaderObject("POST", url, body, timeout, header)
   131  	return resp, respHeader, st, err
   132  }
   133  
   134  func doRequest(method, url, body, timeout string, header map[string]string) (string, string, int, error) {
   135  	t := GetDurationTimeoutRequest(timeout) * time.Second
   136  
   137  	ctx, cls := context.WithTimeout(context.Background(), t)
   138  	defer cls()
   139  
   140  	client := DefaultHTTPClient()
   141  
   142  	message := strings.NewReader(body)
   143  
   144  	req, err := http.NewRequestWithContext(ctx, method, url, message)
   145  	if err != nil {
   146  		return "", "", http.StatusInternalServerError, err
   147  	}
   148  	if header != nil {
   149  		for k, v := range header {
   150  			req.Header.Add(k, v)
   151  		}
   152  	}
   153  	resp, errResp := client.Do(req)
   154  	if errResp != nil {
   155  		return "", "", 0, errResp
   156  	}
   157  	defer resp.Body.Close()
   158  	respHeader := fmt.Sprintf("%v", resp.Header)
   159  
   160  	data, errResponse := ioutil.ReadAll(resp.Body)
   161  	if errResponse != nil {
   162  		return "", respHeader, resp.StatusCode, errResponse
   163  	}
   164  	sData := string(data)
   165  	return sData, respHeader, resp.StatusCode, nil
   166  }
   167  
   168  func doRequestWithHeaderObject(method, url, body, timeout string, header map[string]string) (string, map[string]interface{}, int, error) {
   169  	t := GetDurationTimeoutRequest(timeout) * time.Second
   170  
   171  	ctx, cls := context.WithTimeout(context.Background(), t)
   172  	defer cls()
   173  
   174  	client := DefaultHTTPClient()
   175  
   176  	message := strings.NewReader(body)
   177  
   178  	req, err := http.NewRequestWithContext(ctx, method, url, message)
   179  	if err != nil {
   180  		return "", nil, http.StatusInternalServerError, err
   181  	}
   182  
   183  	for k, v := range header {
   184  		req.Header.Add(k, v)
   185  	}
   186  
   187  	resp, errResp := client.Do(req)
   188  	if errResp != nil {
   189  		return "", nil, 0, errResp
   190  	}
   191  	defer resp.Body.Close()
   192  
   193  	respHeader := convertHeader(resp.Header)
   194  
   195  	data, errResponse := ioutil.ReadAll(resp.Body)
   196  	if errResponse != nil {
   197  		return "", respHeader, resp.StatusCode, errResponse
   198  	}
   199  	sData := string(data)
   200  	return sData, respHeader, resp.StatusCode, nil
   201  }
   202  
   203  func convertHeader(respHeader map[string][]string) map[string]interface{} {
   204  	m2 := make(map[string]interface{}, len(respHeader))
   205  	for k, v := range respHeader {
   206  		m2[k] = v[0]
   207  	}
   208  	return m2
   209  }
   210  
   211  // BuildTLSTransport creates a TLS Client Transport from crt, ca and key files
   212  func BuildTLSTransport(con certificate.TLSCertificate) (*http.Transport, error) {
   213  
   214  	if config.Get().MockMode {
   215  		return nil, nil
   216  	}
   217  
   218  	key, err := certificate.GetCertificateFromStore(con.Key)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	crt, err := certificate.GetCertificateFromStore(con.Crt)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  
   228  	cert, err := tls.X509KeyPair(getCertificateByType(crt), getCertificateByType(key))
   229  
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	transport = &http.Transport{
   235  		Dial:                defaultDialer.Dial,
   236  		TLSHandshakeTimeout: 16 * time.Second,
   237  		TLSClientConfig: &tls.Config{
   238  			Certificates:       []tls.Certificate{cert},
   239  			InsecureSkipVerify: true,
   240  		},
   241  	}
   242  
   243  	return transport, nil
   244  }
   245  
   246  func getCertificateByType(key interface{}) []byte {
   247  	switch v := key.(type) {
   248  	case certificate.SSLCertificate:
   249  		return v.PemData
   250  	default:
   251  		return v.([]byte)
   252  	}
   253  }
   254  
   255  //Sign request
   256  func SignRequest(request string) (string, error) {
   257  
   258  	if icpCert == (certificate.ICPCertificate{}) {
   259  		icp, err := certificate.GetCertificateFromStore(config.Get().CertificateICPName)
   260  		if err != nil {
   261  			return "", err
   262  		}
   263  		icpCert = icp.(certificate.ICPCertificate)
   264  	}
   265  
   266  	signedData, err := s.NewSignedData([]byte(request))
   267  	if err != nil {
   268  		return "", err
   269  	}
   270  
   271  	if err := signedData.AddSigner(icpCert.Certificate, icpCert.RsaPrivateKey, s.SignerInfoConfig{}); err != nil {
   272  		return "", err
   273  	}
   274  
   275  	detachedSignature, err := signedData.Finish()
   276  	if err != nil {
   277  		return "", err
   278  	}
   279  
   280  	signedRequest := base64.StdEncoding.EncodeToString(detachedSignature)
   281  
   282  	return signedRequest, nil
   283  }
   284  
   285  //Read privatekey and parse to PKCS#1
   286  func parsePrivateKey() (crypto.PrivateKey, error) {
   287  
   288  	pkeyBytes, err := ioutil.ReadFile(config.Get().CertICP_PathPkey)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	block, _ := pem.Decode(pkeyBytes)
   294  	if block == nil {
   295  		return nil, errors.New("Key Not Found")
   296  	}
   297  
   298  	switch block.Type {
   299  	case "RSA PRIVATE KEY":
   300  		rsa, err := x509.ParsePKCS1PrivateKey(block.Bytes)
   301  		if err != nil {
   302  			return nil, err
   303  		}
   304  		return rsa, nil
   305  	default:
   306  		return nil, fmt.Errorf("SSH: Unsupported key type %q", block.Type)
   307  	}
   308  
   309  }
   310  
   311  ///Read chainCertificates and adapter to x509.Certificate
   312  func parseChainCertificates() (*x509.Certificate, error) {
   313  
   314  	chainCertsBytes, err := ioutil.ReadFile(config.Get().CertICP_PathChainCertificates)
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  
   319  	block, _ := pem.Decode(chainCertsBytes)
   320  	if block == nil {
   321  		return nil, errors.New("Key Not Found")
   322  	}
   323  
   324  	cert, err := x509.ParseCertificate(block.Bytes)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	return cert, nil
   330  }
   331  
   332  func doRequestTLS(method, url, body, timeout string, header map[string]string, transport *http.Transport) (string, int, error) {
   333  	tlsClient := &http.Client{}
   334  	tlsClient.Transport = transport
   335  	tlsClient.Timeout = GetDurationTimeoutRequest(timeout) * time.Second
   336  	b := strings.NewReader(body)
   337  	req, err := http.NewRequest(method, url, b)
   338  	if err != nil {
   339  		return "", 0, err
   340  	}
   341  
   342  	if header != nil {
   343  		for k, v := range header {
   344  			req.Header.Add(k, v)
   345  		}
   346  	}
   347  	resp, err := tlsClient.Do(req)
   348  	if err != nil {
   349  		return "", 0, err
   350  	}
   351  	defer resp.Body.Close()
   352  	// Dump response
   353  	data, err := ioutil.ReadAll(resp.Body)
   354  	if err != nil {
   355  		return "", 0, err
   356  	}
   357  	sData := string(data)
   358  	return sData, resp.StatusCode, nil
   359  }
   360  
   361  func doRequestTLSWithHeader(method, url, body, timeout string, header map[string]string, transport *http.Transport) (string, map[string]interface{}, int, error) {
   362  	tlsClient := &http.Client{}
   363  	tlsClient.Transport = transport
   364  	tlsClient.Timeout = GetDurationTimeoutRequest(timeout) * time.Second
   365  	b := strings.NewReader(body)
   366  	req, err := http.NewRequest(method, url, b)
   367  	if err != nil {
   368  		return "", nil, 0, err
   369  	}
   370  
   371  	for k, v := range header {
   372  		req.Header.Add(k, v)
   373  	}
   374  
   375  	resp, err := tlsClient.Do(req)
   376  	if err != nil {
   377  		return "", nil, 0, err
   378  	}
   379  	respHeader := convertHeader(resp.Header)
   380  	defer resp.Body.Close()
   381  	// Dump response
   382  	data, err := ioutil.ReadAll(resp.Body)
   383  	if err != nil {
   384  		return "", nil, 0, err
   385  	}
   386  	sData := string(data)
   387  	return sData, respHeader, resp.StatusCode, nil
   388  }
   389  
   390  func PostTLS(url, body, timeout string, header map[string]string, transport *http.Transport) (string, int, error) {
   391  	return doRequestTLS("POST", url, body, timeout, header, transport)
   392  }
   393  
   394  func PostTLSWithHeader(url, body, timeout string, header map[string]string, transport *http.Transport) (string, map[string]interface{}, int, error) {
   395  	return doRequestTLSWithHeader("POST", url, body, timeout, header, transport)
   396  }
   397  
   398  //HeaderToMap converte um http Header para um dicionário string -> string
   399  func HeaderToMap(header http.Header) map[string]string {
   400  	headerMap := make(map[string]string)
   401  	for key, value := range header {
   402  		if key == "Authorization" {
   403  			headerMap[key] = "[REDACTED]"
   404  		} else {
   405  			headerMap[key] = value[0]
   406  		}
   407  	}
   408  	return headerMap
   409  }