ioutil package was deprecated in Go 1.16, replace all uses with their respective replacements. This patch was generated with a combination of `gofmt -r`, `eg`, and manually (for `ioutil.ReadDir`).
199 lines
4.1 KiB
Go
199 lines
4.1 KiB
Go
//go:build !coverage
|
|
// +build !coverage
|
|
|
|
// Fetch an URL, and check if the response matches what we expect.
|
|
//
|
|
// Useful for testing HTTP(s) servers.
|
|
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var exitCode int
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
fatalf("Usage: fexp <URL> <options>\n")
|
|
}
|
|
|
|
// The first arg is the URL, and then we shift.
|
|
url := os.Args[1]
|
|
os.Args = append([]string{os.Args[0]}, os.Args[2:]...)
|
|
|
|
var (
|
|
body = flag.String("body", "",
|
|
"expect body with these exact contents")
|
|
bodyRE = flag.String("bodyre", "",
|
|
"expect body matching these contents (regexp match)")
|
|
bodyNotRE = flag.String("bodynotre", "",
|
|
"expect body NOT matching these contents (regexp match)")
|
|
redir = flag.String("redir", "",
|
|
"expect a redirect to this URL")
|
|
status = flag.Int("status", 200,
|
|
"expect this status code")
|
|
verbose = flag.Bool("v", false,
|
|
"enable verbose output")
|
|
save = flag.String("save", "",
|
|
"save body to this file")
|
|
method = flag.String("method", "GET",
|
|
"request method to use")
|
|
hdrRE = flag.String("hdrre", "",
|
|
"expect a header matching these contents (regexp match)")
|
|
caCert = flag.String("cacert", "",
|
|
"file to read CA cert from")
|
|
)
|
|
flag.Parse()
|
|
|
|
client := &http.Client{
|
|
CheckRedirect: noRedirect,
|
|
Transport: mkTransport(*caCert),
|
|
}
|
|
|
|
req, err := http.NewRequest(*method, url, nil)
|
|
if err != nil {
|
|
fatalf("error building request: %q", err)
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
fatalf("error getting %q: %v\n", url, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
rbody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
errorf("error reading body: %v\n", err)
|
|
}
|
|
|
|
if *save != "" {
|
|
err = os.WriteFile(*save, rbody, 0664)
|
|
if err != nil {
|
|
errorf("error writing body to file %q: %v\n", *save, err)
|
|
}
|
|
}
|
|
|
|
if *verbose {
|
|
fmt.Printf("Request: %s\n", url)
|
|
fmt.Printf("Response:\n")
|
|
fmt.Printf(" %v %v\n", resp.Proto, resp.Status)
|
|
ks := []string{}
|
|
for k := range resp.Header {
|
|
ks = append(ks, k)
|
|
}
|
|
sort.Strings(ks)
|
|
for _, k := range ks {
|
|
fmt.Printf(" %v: %s\n", k,
|
|
strings.Join(resp.Header[k], ", "))
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
|
|
if resp.StatusCode != *status {
|
|
errorf("status is not %d: %q\n", *status, resp.Status)
|
|
}
|
|
|
|
if *body != "" {
|
|
// Unescape the body to allow control characters more easily.
|
|
*body, _ = strconv.Unquote("\"" + *body + "\"")
|
|
if string(rbody) != *body {
|
|
errorf("unexpected body: %q\n", rbody)
|
|
}
|
|
}
|
|
|
|
if *bodyRE != "" {
|
|
matched, err := regexp.Match(*bodyRE, rbody)
|
|
if err != nil {
|
|
errorf("regexp error: %q\n", err)
|
|
}
|
|
if !matched {
|
|
errorf("body did not match regexp: %q\n", rbody)
|
|
}
|
|
}
|
|
|
|
if *bodyNotRE != "" {
|
|
matched, err := regexp.Match(*bodyNotRE, rbody)
|
|
if err != nil {
|
|
errorf("regexp error: %q\n", err)
|
|
}
|
|
if matched {
|
|
errorf("body matched regexp: %q\n", rbody)
|
|
}
|
|
}
|
|
|
|
if *redir != "" {
|
|
if loc := resp.Header.Get("Location"); loc != *redir {
|
|
errorf("unexpected redir location: %q\n", loc)
|
|
}
|
|
}
|
|
|
|
if *hdrRE != "" {
|
|
match := false
|
|
outer:
|
|
for k, vs := range resp.Header {
|
|
for _, v := range vs {
|
|
hdr := fmt.Sprintf("%s: %s", k, v)
|
|
matched, err := regexp.MatchString(*hdrRE, hdr)
|
|
if err != nil {
|
|
errorf("regexp error: %q\n", err)
|
|
}
|
|
if matched {
|
|
match = true
|
|
break outer
|
|
}
|
|
}
|
|
}
|
|
|
|
if !match {
|
|
errorf("header did not match: %v\n", resp.Header)
|
|
}
|
|
}
|
|
|
|
os.Exit(exitCode)
|
|
}
|
|
|
|
func noRedirect(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
}
|
|
|
|
func mkTransport(caCert string) http.RoundTripper {
|
|
if caCert == "" {
|
|
return nil
|
|
}
|
|
|
|
certs, err := os.ReadFile(caCert)
|
|
if err != nil {
|
|
fatalf("error reading CA file %q: %v\n", caCert, err)
|
|
}
|
|
|
|
rootCAs := x509.NewCertPool()
|
|
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
|
|
fatalf("error adding certs to root\n")
|
|
}
|
|
|
|
return &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
RootCAs: rootCAs,
|
|
},
|
|
}
|
|
}
|
|
|
|
func fatalf(s string, a ...interface{}) {
|
|
fmt.Fprintf(os.Stderr, s, a...)
|
|
os.Exit(1)
|
|
}
|
|
|
|
func errorf(s string, a ...interface{}) {
|
|
fmt.Fprintf(os.Stderr, s, a...)
|
|
exitCode = 1
|
|
}
|