]> ruderich.org/simon Gitweb - nsscash/nsscash.git/blob - fetch.go
.github: update upstream actions to latest version
[nsscash/nsscash.git] / fetch.go
1 // Download files via HTTP with support for If-Modified-Since
2
3 // Copyright (C) 2019-2021  Simon Ruderich
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU Affero General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program 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
13 // GNU Affero General Public License for more details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
18 package main
19
20 import (
21         "crypto/tls"
22         "crypto/x509"
23         "fmt"
24         "io/ioutil"
25         "net/http"
26         "time"
27
28         "github.com/pkg/errors"
29 )
30
31 // Global variable to permit reuse of connections (keep-alive)
32 var clients map[string]*http.Client
33
34 func init() {
35         clients = make(map[string]*http.Client)
36         clients[""] = &http.Client{}
37 }
38
39 func fetchIfModified(url, user, pass, ca string, lastModified *time.Time) (int, []byte, error) {
40         req, err := http.NewRequest("GET", url, nil)
41         if err != nil {
42                 return 0, nil, err
43         }
44         if user != "" || pass != "" {
45                 req.SetBasicAuth(user, pass)
46         }
47         if !lastModified.IsZero() {
48                 req.Header.Add("If-Modified-Since",
49                         lastModified.UTC().Format(http.TimeFormat))
50         }
51
52         client, ok := clients[ca]
53         if !ok {
54                 pem, err := ioutil.ReadFile(ca)
55                 if err != nil {
56                         return 0, nil, errors.Wrapf(err, "file.ca %q", ca)
57                 }
58                 pool := x509.NewCertPool()
59                 ok := pool.AppendCertsFromPEM(pem)
60                 if !ok {
61                         return 0, nil, fmt.Errorf(
62                                 "file.ca %q: no PEM cert found", ca)
63                 }
64
65                 client = &http.Client{
66                         Transport: &http.Transport{
67                                 TLSClientConfig: &tls.Config{
68                                         RootCAs: pool,
69                                 },
70                         },
71                 }
72                 clients[ca] = client
73         }
74
75         resp, err := client.Do(req)
76         if err != nil {
77                 return 0, nil, err
78         }
79         defer resp.Body.Close()
80
81         body, err := ioutil.ReadAll(resp.Body)
82         if err != nil {
83                 return 0, nil, err
84         }
85
86         modified, err := http.ParseTime(resp.Header.Get("Last-Modified"))
87         if err == nil {
88                 *lastModified = modified
89         }
90
91         return resp.StatusCode, body, nil
92 }