-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
140 lines (117 loc) · 3.32 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"crypto/sha512"
"encoding/hex"
"encoding/json"
"flag"
"io/ioutil"
"log"
"net/http"
neturl "net/url"
"time"
"github.com/PuerkitoBio/goquery"
)
var url, cftkn, email, cfsite, Version string
var dryrun, version bool
var interval int
func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
flag.StringVar(&cftkn, "cftkn", "", "Cloudflare API token")
flag.StringVar(&cfsite, "cfsite", "", "The name of the site to purge in Cloudflare")
flag.StringVar(&email, "email", "", "Cloudflare account email")
flag.StringVar(&url, "url", "", "The url to watch for changes")
flag.BoolVar(&dryrun, "dryrun", false, "Simulates a purging without hitting Cloudflare.")
flag.BoolVar(&version, "version", false, "Prints version")
flag.IntVar(&interval, "interval", 15, "The time in seconds to check for changes.")
}
var lastChecksum string
func main() {
flag.Parse()
if version {
log.Println(Version)
return
}
if url == "" || cftkn == "" || email == "" || cfsite == "" {
flag.Usage()
return
}
c := time.Tick(time.Duration(interval) * time.Second)
log.Printf("[INFO] Waiting %d seconds...", interval)
for _ = range c {
log.Printf("[INFO] Checking for changes in %s", url)
if res, ok := check(url); ok {
log.Printf("[INFO] Cloudflare response: %+v", res)
}
log.Printf("[INFO] Waiting %d seconds...", interval)
}
}
type CFResponse struct {
Result string `json:"result"`
Message string `json:"msg"`
}
func purge(url string) (*CFResponse, bool) {
u, err := neturl.Parse(url)
if err != nil {
log.Printf("[ERROR] %#v", err)
return nil, false
}
// Full purge by default unless the URL has a path
params := neturl.Values{
"a": {"fpurge_ts"},
"tkn": {cftkn},
"email": {email},
"z": {cfsite},
"v": {"1"},
}
// Purges only the resource pointed by the url
if u.Path != "" {
params.Set("a", "zone_file_purge")
params.Add("url", url)
}
if dryrun {
log.Printf("[INFO] DryRun successful")
return &CFResponse{Result: "success"}, true
}
resp, err := http.PostForm("https://www.cloudflare.com/api_json.html", params)
if err != nil {
log.Printf("[ERROR] %#v", err)
return nil, false
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("[ERROR] %#v", err)
return nil, false
}
cfresp := new(CFResponse)
err = json.Unmarshal(body, cfresp)
if err != nil {
log.Printf("[ERROR] %#v", err)
log.Printf("[DEBUG] Response: %s", string(body[:]))
return nil, false
}
return cfresp, true
}
func check(url string) (*CFResponse, bool) {
// We need to get the body of the document because getting the raw content
// of the response will always be different due to Cloudflare code injections.
doc, err := goquery.NewDocument(url)
if err != nil {
log.Printf("[ERROR] %#v\n", err)
return nil, false
}
hash := sha512.New()
hash.Write([]byte(doc.Find("body").Text()))
md := hash.Sum(nil)
checksum := hex.EncodeToString(md)
if lastChecksum == checksum {
return nil, false
}
log.Printf("[INFO] Checksum: %s", checksum)
lastChecksum = checksum
log.Printf("[INFO] %s changed, purging Cloudflare cache...", url)
return purge(url)
}