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 }