mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-02-23 23:23:05 +08:00
175 lines
6.3 KiB
Go
175 lines
6.3 KiB
Go
package models
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
// TranscodePRtoSR takes a PAGE_RULE record, stores transcoded versions of the fields, and makes the record a CLOUDFLAREAPI_SINGLE_REDDIRECT.
|
|
func TranscodePRtoSR(rec *RecordConfig) error {
|
|
rec.Type = "CF_SINGLE_REDIRECT" // This record is now a CF_SINGLE_REDIRECT
|
|
|
|
// Extract the fields we're reading from:
|
|
sr := rec.AsCFSINGLEREDIRECT()
|
|
code := sr.Code
|
|
prWhen := sr.PRWhen
|
|
prThen := sr.PRThen
|
|
srName := sr.PRDisplay
|
|
|
|
// Convert old-style patterns to new-style rules:
|
|
srWhen, srThen, err := makeRuleFromPattern(prWhen, prThen)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Fix the RecordConfig
|
|
return makeSingleRedirectFromConvert(rec,
|
|
sr.PRPriority,
|
|
prWhen, prThen,
|
|
code,
|
|
srName, srWhen, srThen)
|
|
}
|
|
|
|
// makeRuleFromPattern compile old-style patterns and replacements into new-style rules and expressions.
|
|
func makeRuleFromPattern(pattern, replacement string) (string, string, error) {
|
|
var srWhen, srThen string
|
|
var err error
|
|
|
|
var phost, ppath string
|
|
origPattern := pattern
|
|
pattern, phost, ppath, err = normalizeURL(pattern)
|
|
_ = pattern
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
var rhost, rpath string
|
|
origReplacement := replacement
|
|
replacement, rhost, rpath, err = normalizeURL(replacement)
|
|
_ = rpath
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
// TODO(tlim): This could be a lot faster by not repeating itself so much.
|
|
// However I want to get it working before it is optimized.
|
|
|
|
// "pr" is Page Rule (old style)
|
|
// "sr" is Static Rule (new style)
|
|
// prWhen + prThen is the old-style matching pattern and replacement pattern.
|
|
// srWhen + srThen is the new-style matching rule and replacement expression.
|
|
|
|
if !strings.Contains(phost, `*`) && (ppath == `/` || ppath == "") {
|
|
// https://i.sstatic.net/ (No Wildcards)
|
|
srWhen = fmt.Sprintf(`http.host eq "%s" and http.request.uri.path eq "%s"`, phost, "/")
|
|
} else if !strings.Contains(phost, `*`) && (ppath == `/*`) {
|
|
// https://i.stack.imgur.com/*
|
|
srWhen = fmt.Sprintf(`http.host eq "%s"`, phost)
|
|
} else if !strings.Contains(phost, `*`) && !strings.Contains(ppath, "*") {
|
|
// https://insights.stackoverflow.com/trends
|
|
srWhen = fmt.Sprintf(`http.host eq "%s" and http.request.uri.path eq "%s"`, phost, ppath)
|
|
} else if phost[0] == '*' && strings.Count(phost, `*`) == 1 && !strings.Contains(ppath, "*") {
|
|
// *stackoverflow.careers/ (wildcard at beginning only)
|
|
srWhen = fmt.Sprintf(`( http.host eq "%s" or ends_with(http.host, ".%s") ) and http.request.uri.path eq "%s"`, phost[1:], phost[1:], ppath)
|
|
} else if phost[0] == '*' && strings.Count(phost, `*`) == 1 && ppath == "/*" {
|
|
// *stackoverflow.careers/* (wildcard at beginning and end)
|
|
srWhen = fmt.Sprintf(`http.host eq "%s" or ends_with(http.host, ".%s")`, phost[1:], phost[1:])
|
|
} else if strings.Contains(phost, `*`) && ppath == "/*" {
|
|
// meta.*yodeya.com/* (wildcard in host)
|
|
h := simpleGlobToRegex(phost)
|
|
srWhen = fmt.Sprintf(`http.host matches r###"%s"###`, h)
|
|
} else if !strings.Contains(phost, `*`) && strings.Count(ppath, `*`) == 1 && strings.HasSuffix(ppath, "*") {
|
|
// domain.tld/.well-known* (wildcard in path)
|
|
srWhen = fmt.Sprintf(`(starts_with(http.request.uri.path, "%s") and http.host eq "%s")`,
|
|
ppath[0:len(ppath)-1],
|
|
phost)
|
|
}
|
|
|
|
// replacement
|
|
|
|
if !strings.Contains(replacement, `$`) {
|
|
// https://stackexchange.com/ (no substitutions)
|
|
srThen = fmt.Sprintf(`concat("%s", "")`, replacement)
|
|
} else if phost[0] == '*' && strings.Count(phost, `*`) == 1 && strings.Count(replacement, `$`) == 1 && len(rpath) > 3 && strings.HasSuffix(rpath, "/$2") {
|
|
// *stackoverflowenterprise.com/* -> https://www.stackoverflowbusiness.com/enterprise/$2
|
|
srThen = fmt.Sprintf(`concat("https://%s", "%s", http.request.uri.path)`,
|
|
rhost,
|
|
rpath[0:len(rpath)-3],
|
|
)
|
|
} else if phost[0] == '*' && strings.Count(phost, `*`) == 1 && strings.Count(replacement, `$`) == 1 && len(rpath) > 3 && strings.HasSuffix(rpath, "/$2") {
|
|
// *stackoverflowenterprise.com/* -> https://www.stackoverflowbusiness.com/enterprise/$2
|
|
srThen = fmt.Sprintf(`concat("https://%s", "%s", http.request.uri.path)`,
|
|
rhost,
|
|
rpath[0:len(rpath)-3],
|
|
)
|
|
} else if strings.Count(replacement, `$`) == 1 && rpath == `/$1` {
|
|
// https://i.sstatic.net/$1 ($1 at end)
|
|
srThen = fmt.Sprintf(`concat("https://%s", http.request.uri.path)`, rhost)
|
|
} else if strings.Count(phost, `*`) == 1 && strings.Count(ppath, `*`) == 1 &&
|
|
strings.Count(replacement, `$`) == 1 && strings.HasSuffix(rpath, `/$2`) {
|
|
// https://careers.stackoverflow.com/$2
|
|
srThen = fmt.Sprintf(`concat("https://%s", http.request.uri.path)`, rhost)
|
|
} else if strings.Count(replacement, `$`) == 1 && strings.HasSuffix(replacement, `$1`) {
|
|
// https://social.domain.tld/.well-known$1
|
|
srThen = fmt.Sprintf(`concat("https://%s", http.request.uri.path)`, rhost)
|
|
} else if strings.Count(replacement, `$`) == 1 && strings.HasSuffix(replacement, `$1`) {
|
|
// https://social.domain.tld/.well-known$1
|
|
srThen = fmt.Sprintf(`concat("https://%s", http.request.uri.path)`, rhost)
|
|
}
|
|
|
|
// Not implemented
|
|
|
|
if srWhen == "" {
|
|
return "", "", fmt.Errorf("conversion not implemented for pattern: %s", origPattern)
|
|
}
|
|
if srThen == "" {
|
|
return "", "", fmt.Errorf("conversion not implemented for replacement: %s", origReplacement)
|
|
}
|
|
|
|
return srWhen, srThen, nil
|
|
}
|
|
|
|
// normalizeURL turns foo.com into https://foo.com and replaces HTTP with HTTPS.
|
|
// It also returns an error if there is a port specified (like :8080)
|
|
func normalizeURL(s string) (string, string, string, error) {
|
|
orig := s
|
|
if strings.HasPrefix(s, `http://`) {
|
|
s = "https://" + s[7:]
|
|
} else if !strings.HasPrefix(s, `https://`) {
|
|
s = `https://` + s
|
|
}
|
|
|
|
// Make sure it parses.
|
|
u, err := url.Parse(s)
|
|
if err != nil {
|
|
return "", "", "", err
|
|
}
|
|
|
|
// Make sure it doesn't have a port (https://example.com:8080)
|
|
_, port, _ := net.SplitHostPort(u.Host)
|
|
if port != "" {
|
|
return "", "", "", fmt.Errorf("unimplemented port: %q", orig)
|
|
}
|
|
|
|
return s, u.Host, u.Path, nil
|
|
}
|
|
|
|
// simpleGlobToRegex translates very simple Glob patterns into regexp-compatible expressions.
|
|
// It only handles `.` and `*` currently. See singleredirect_test.go for supported patterns.
|
|
func simpleGlobToRegex(g string) string {
|
|
if g == "" {
|
|
return `.*`
|
|
}
|
|
|
|
if !strings.HasSuffix(g, "*") {
|
|
g = g + `$`
|
|
}
|
|
if !strings.HasPrefix(g, "*") {
|
|
g = `^` + g
|
|
}
|
|
|
|
g = strings.ReplaceAll(g, `.`, `\.`)
|
|
g = strings.ReplaceAll(g, `*`, `.*`)
|
|
return g
|
|
}
|