github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/common/web/web.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package web
    20  
    21  import (
    22  	"bytes"
    23  	"encoding/base64"
    24  	"fmt"
    25  	"io"
    26  	"net"
    27  	"net/http"
    28  	"os"
    29  	"time"
    30  
    31  	"github.com/e154/smart-home/common/apperr"
    32  	"github.com/e154/smart-home/common/logger"
    33  )
    34  
    35  var (
    36  	log = logger.MustGetLogger("web")
    37  )
    38  
    39  type crawler struct {
    40  	headers            []map[string]string
    41  	digestAuth         bool
    42  	basicAuth          bool
    43  	username, password string
    44  	dig                *DigestHeaders
    45  }
    46  
    47  func New() Crawler {
    48  	return &crawler{}
    49  }
    50  
    51  func (c *crawler) BasicAuth(username, password string) Crawler {
    52  	c.username = username
    53  	c.password = password
    54  	c.basicAuth = true
    55  	return c
    56  }
    57  
    58  func (c *crawler) DigestAuth(username, password string) Crawler {
    59  	c.username = username
    60  	c.password = password
    61  	c.digestAuth = true
    62  	return c
    63  }
    64  
    65  // Probe ...
    66  func (c *crawler) Probe(options Request) (status int, body []byte, err error) {
    67  
    68  	var req *http.Request
    69  	if req, err = c.prepareRequest(options); err != nil {
    70  		return
    71  	}
    72  
    73  	if err = c.auth(req, options.Url); err != nil {
    74  		return
    75  	}
    76  
    77  	var timeout = time.Second * 2
    78  	if options.Timeout > 0 {
    79  		timeout = options.Timeout
    80  	}
    81  
    82  	var resp *http.Response
    83  	if resp, err = c.doIt(req, timeout); err != nil {
    84  		return
    85  	}
    86  
    87  	status = resp.StatusCode
    88  
    89  	defer resp.Body.Close()
    90  	body, err = io.ReadAll(resp.Body)
    91  
    92  	return
    93  }
    94  
    95  // Probe ...
    96  func (c *crawler) Download(options Request) (filePath string, err error) {
    97  
    98  	defer func() {
    99  		if err != nil {
   100  			fmt.Printf("%v", err.Error())
   101  		}
   102  	}()
   103  
   104  	var req *http.Request
   105  	if req, err = c.prepareRequest(options); err != nil {
   106  		return
   107  	}
   108  
   109  	if err = c.auth(req, options.Url); err != nil {
   110  		return
   111  	}
   112  
   113  	var timeout = time.Second * 2
   114  	if options.Timeout > 0 {
   115  		timeout = options.Timeout
   116  	}
   117  
   118  	var resp *http.Response
   119  	if resp, err = c.doIt(req, timeout); err != nil {
   120  		return
   121  	}
   122  
   123  	defer resp.Body.Close()
   124  
   125  	var f *os.File
   126  	f, err = os.CreateTemp("/tmp", "*.jpeg")
   127  	if err != nil {
   128  		return
   129  	}
   130  
   131  	filePath = f.Name()
   132  	if _, err = io.Copy(f, resp.Body); err != nil {
   133  		return
   134  	}
   135  
   136  	log.Infof("file downloaded to '%s' ...", filePath)
   137  
   138  	return
   139  }
   140  
   141  func (c *crawler) doIt(req *http.Request, timeout time.Duration) (resp *http.Response, err error) {
   142  
   143  	netTransport := &http.Transport{
   144  		DialContext: (&net.Dialer{
   145  			Timeout: timeout,
   146  		}).DialContext,
   147  		TLSHandshakeTimeout: timeout,
   148  	}
   149  
   150  	client := &http.Client{
   151  		Timeout:   timeout,
   152  		Transport: netTransport,
   153  	}
   154  
   155  	resp, err = client.Do(req)
   156  
   157  	return
   158  }
   159  
   160  func (c *crawler) prepareRequest(options Request) (req *http.Request, err error) {
   161  
   162  	if options.Url == "" {
   163  		err = apperr.ErrBadRequestParams
   164  		return
   165  	}
   166  
   167  	var r io.Reader = nil
   168  	switch options.Method {
   169  	case "POST", "PUT", "UPDATE", "PATCH":
   170  		if options.Body != nil && len(options.Body) > 0 {
   171  			r = bytes.NewReader(options.Body)
   172  		}
   173  	}
   174  
   175  	if req, err = http.NewRequest(options.Method, options.Url, r); err != nil {
   176  		return
   177  	}
   178  
   179  	req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15")
   180  
   181  	// set headers
   182  	for _, values := range options.Headers {
   183  		for k, v := range values {
   184  			req.Header.Set(k, v)
   185  		}
   186  	}
   187  
   188  	return
   189  }
   190  
   191  func (c *crawler) auth(req *http.Request, uri string) (err error) {
   192  
   193  	if c.basicAuth {
   194  		auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", c.username, c.password)))
   195  		c.headers = append(c.headers, map[string]string{
   196  			"Authorization": "Basic " + auth,
   197  		})
   198  		req.Header.Add("Authorization", "Basic "+auth)
   199  	}
   200  
   201  	if c.digestAuth || c.dig != nil {
   202  		c.dig = &DigestHeaders{}
   203  		c.dig, err = c.dig.Auth(c.username, c.password, uri)
   204  		if err != nil {
   205  			return
   206  		}
   207  		c.dig.ApplyAuth(req)
   208  	}
   209  	return err
   210  }