]> ruderich.org/simon Gitweb - nsscash/nsscash.git/blob - fetch.go
nsscash: add "ca" option for files
[nsscash/nsscash.git] / fetch.go
1 // Download files via HTTP with support for If-Modified-Since
2
3 // Copyright (C) 2019  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, 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 !lastModified.IsZero() {
45                 req.Header.Add("If-Modified-Since",
46                         lastModified.Format(http.TimeFormat))
47         }
48
49         client, ok := clients[ca]
50         if !ok {
51                 pem, err := ioutil.ReadFile(ca)
52                 if err != nil {
53                         return 0, nil, errors.Wrapf(err, "file.ca %q", ca)
54                 }
55                 pool := x509.NewCertPool()
56                 ok := pool.AppendCertsFromPEM(pem)
57                 if !ok {
58                         return 0, nil, fmt.Errorf(
59                                 "file.ca %q: no PEM cert found", ca)
60                 }
61
62                 client = &http.Client{
63                         Transport: &http.Transport{
64                                 TLSClientConfig: &tls.Config{
65                                         RootCAs: pool,
66                                 },
67                         },
68                 }
69                 clients[ca] = client
70         }
71
72         resp, err := client.Do(req)
73         if err != nil {
74                 return 0, nil, err
75         }
76         defer resp.Body.Close()
77
78         body, err := ioutil.ReadAll(resp.Body)
79         if err != nil {
80                 return 0, nil, err
81         }
82
83         modified, err := http.ParseTime(resp.Header.Get("Last-Modified"))
84         if err == nil {
85                 *lastModified = modified
86         }
87
88         return resp.StatusCode, body, nil
89 }