mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-11-08 18:04:09 +01:00
Fixed #874
- Added exact match for redirection feature - Added case sensitive check for redirection - Updated version number
This commit is contained in:
@@ -103,6 +103,7 @@ func RegisterRedirectionAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
||||
authRouter.HandleFunc("/api/redirect/edit", handleEditRedirectionRule)
|
||||
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
||||
authRouter.HandleFunc("/api/redirect/case_sensitive", handleToggleRedirectCaseSensitivity)
|
||||
}
|
||||
|
||||
// Register the APIs for access rules management functions
|
||||
|
||||
@@ -44,7 +44,7 @@ import (
|
||||
const (
|
||||
/* Build Constants */
|
||||
SYSTEM_NAME = "Zoraxy"
|
||||
SYSTEM_VERSION = "3.2.9"
|
||||
SYSTEM_VERSION = "3.3.0"
|
||||
DEVELOPMENT_BUILD = false
|
||||
|
||||
/* System Constants */
|
||||
@@ -119,10 +119,15 @@ var (
|
||||
|
||||
/* Global Variables and Handlers */
|
||||
var (
|
||||
/* System */
|
||||
nodeUUID = "generic" //System uuid in uuidv4 format, load from database on startup
|
||||
bootTime = time.Now().Unix()
|
||||
requireAuth = true //Require authentication for webmin panel, override from flag
|
||||
|
||||
/* mDNS */
|
||||
previousmdnsScanResults = []*mdns.NetworkHost{}
|
||||
mdnsTickerStop chan bool
|
||||
|
||||
/*
|
||||
Binary Embedding File System
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ package redirection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
@@ -15,26 +16,28 @@ import (
|
||||
)
|
||||
|
||||
type RuleTable struct {
|
||||
AllowRegex bool //Allow regular expression to be used in rule matching. Require up to O(n^m) time complexity
|
||||
Logger *logger.Logger
|
||||
configPath string //The location where the redirection rules is stored
|
||||
rules sync.Map //Store the redirection rules for this reverse proxy instance
|
||||
|
||||
AllowRegex bool //Allow regular expression to be used in rule matching. Require up to O(n^m) time complexity
|
||||
CaseSensitive bool //Force case sensitive URL matching
|
||||
configPath string //The location where the redirection rules is stored
|
||||
rules sync.Map //Store map[string]*RedirectRules for this reverse proxy instance
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
type RedirectRules struct {
|
||||
RedirectURL string //The matching URL to redirect
|
||||
TargetURL string //The destination redirection url
|
||||
ForwardChildpath bool //Also redirect the pathname
|
||||
StatusCode int //Status Code for redirection
|
||||
RedirectURL string //The matching URL to redirect
|
||||
TargetURL string //The destination redirection url
|
||||
ForwardChildpath bool //Also redirect the pathname
|
||||
StatusCode int //Status Code for redirection
|
||||
RequireExactMatch bool //Require exact URL match instead of prefix matching
|
||||
}
|
||||
|
||||
func NewRuleTable(configPath string, allowRegex bool, logger *logger.Logger) (*RuleTable, error) {
|
||||
func NewRuleTable(configPath string, allowRegex bool, caseSensitive bool, logger *logger.Logger) (*RuleTable, error) {
|
||||
thisRuleTable := RuleTable{
|
||||
rules: sync.Map{},
|
||||
configPath: configPath,
|
||||
AllowRegex: allowRegex,
|
||||
Logger: logger,
|
||||
rules: sync.Map{},
|
||||
configPath: configPath,
|
||||
AllowRegex: allowRegex,
|
||||
CaseSensitive: caseSensitive,
|
||||
Logger: logger,
|
||||
}
|
||||
//Load all the rules from the config path
|
||||
if !utils.FileExists(configPath) {
|
||||
@@ -74,13 +77,14 @@ func NewRuleTable(configPath string, allowRegex bool, logger *logger.Logger) (*R
|
||||
return &thisRuleTable, nil
|
||||
}
|
||||
|
||||
func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardPathname bool, statusCode int) error {
|
||||
func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardPathname bool, statusCode int, requireExactMatch bool) error {
|
||||
// Create a new RedirectRules object with the given parameters
|
||||
newRule := &RedirectRules{
|
||||
RedirectURL: redirectURL,
|
||||
TargetURL: destURL,
|
||||
ForwardChildpath: forwardPathname,
|
||||
StatusCode: statusCode,
|
||||
RedirectURL: redirectURL,
|
||||
TargetURL: destURL,
|
||||
ForwardChildpath: forwardPathname,
|
||||
StatusCode: statusCode,
|
||||
RequireExactMatch: requireExactMatch,
|
||||
}
|
||||
|
||||
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
||||
@@ -111,12 +115,13 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
||||
}
|
||||
|
||||
// Edit an existing redirection rule, the oldRedirectURL is used to find the rule to be edited
|
||||
func (t *RuleTable) EditRedirectRule(oldRedirectURL string, newRedirectURL string, destURL string, forwardPathname bool, statusCode int) error {
|
||||
func (t *RuleTable) EditRedirectRule(oldRedirectURL string, newRedirectURL string, destURL string, forwardPathname bool, statusCode int, requireExactMatch bool) error {
|
||||
newRule := &RedirectRules{
|
||||
RedirectURL: newRedirectURL,
|
||||
TargetURL: destURL,
|
||||
ForwardChildpath: forwardPathname,
|
||||
StatusCode: statusCode,
|
||||
RedirectURL: newRedirectURL,
|
||||
TargetURL: destURL,
|
||||
ForwardChildpath: forwardPathname,
|
||||
StatusCode: statusCode,
|
||||
RequireExactMatch: requireExactMatch,
|
||||
}
|
||||
|
||||
//Remove the old rule
|
||||
@@ -189,28 +194,56 @@ func (t *RuleTable) MatchRedirectRule(requestedURL string) *RedirectRules {
|
||||
var targetRedirectionRule *RedirectRules = nil
|
||||
var maxMatch int = 0
|
||||
t.rules.Range(func(key interface{}, value interface{}) bool {
|
||||
// Check if the requested URL starts with the key as a prefix
|
||||
rule := value.(*RedirectRules)
|
||||
keyStr := key.(string)
|
||||
|
||||
if t.AllowRegex {
|
||||
//Regexp matching rule
|
||||
matched, err := regexp.MatchString(key.(string), requestedURL)
|
||||
matched, err := regexp.MatchString(keyStr, requestedURL)
|
||||
if err != nil {
|
||||
//Something wrong with the regex?
|
||||
t.log("Unable to match regex", err)
|
||||
return true
|
||||
}
|
||||
if matched {
|
||||
maxMatch = len(key.(string))
|
||||
targetRedirectionRule = value.(*RedirectRules)
|
||||
maxMatch = len(keyStr)
|
||||
targetRedirectionRule = rule
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//Check matching based on exact match requirement
|
||||
var matched bool
|
||||
if rule.RequireExactMatch {
|
||||
fmt.Println(requestedURL, keyStr)
|
||||
//Exact match required
|
||||
if t.CaseSensitive {
|
||||
matched = requestedURL == keyStr
|
||||
} else {
|
||||
matched = strings.EqualFold(requestedURL, keyStr)
|
||||
}
|
||||
if !matched {
|
||||
//Also check for trailing slash case
|
||||
if t.CaseSensitive {
|
||||
matched = requestedURL == keyStr+"/"
|
||||
} else {
|
||||
matched = strings.EqualFold(requestedURL, keyStr+"/")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Default: prefix matching redirect
|
||||
if strings.HasPrefix(requestedURL, key.(string)) {
|
||||
// This request URL matched the domain
|
||||
if len(key.(string)) > maxMatch {
|
||||
maxMatch = len(key.(string))
|
||||
targetRedirectionRule = value.(*RedirectRules)
|
||||
}
|
||||
if t.CaseSensitive {
|
||||
matched = strings.HasPrefix(requestedURL, keyStr)
|
||||
} else {
|
||||
matched = strings.HasPrefix(strings.ToLower(requestedURL), strings.ToLower(keyStr))
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
// This request URL matched the rule
|
||||
if len(keyStr) > maxMatch {
|
||||
maxMatch = len(keyStr)
|
||||
targetRedirectionRule = rule
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,12 @@ func handleAddRedirectionRule(w http.ResponseWriter, r *http.Request) {
|
||||
forwardChildpath = "true"
|
||||
}
|
||||
|
||||
requireExactMatch, err := utils.PostPara(r, "requireExactMatch")
|
||||
if err != nil {
|
||||
//Assume false
|
||||
requireExactMatch = "false"
|
||||
}
|
||||
|
||||
redirectTypeString, err := utils.PostPara(r, "redirectType")
|
||||
if err != nil {
|
||||
redirectTypeString = "307"
|
||||
@@ -52,7 +58,7 @@ func handleAddRedirectionRule(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = redirectTable.AddRedirectRule(redirectUrl, destUrl, forwardChildpath == "true", redirectionStatusCode)
|
||||
err = redirectTable.AddRedirectRule(redirectUrl, destUrl, forwardChildpath == "true", redirectionStatusCode, requireExactMatch == "true")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
@@ -101,6 +107,12 @@ func handleEditRedirectionRule(w http.ResponseWriter, r *http.Request) {
|
||||
forwardChildpath = "true"
|
||||
}
|
||||
|
||||
requireExactMatch, err := utils.PostPara(r, "requireExactMatch")
|
||||
if err != nil {
|
||||
//Assume false
|
||||
requireExactMatch = "false"
|
||||
}
|
||||
|
||||
redirectTypeString, err := utils.PostPara(r, "redirectType")
|
||||
if err != nil {
|
||||
redirectTypeString = "307"
|
||||
@@ -112,7 +124,7 @@ func handleEditRedirectionRule(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = redirectTable.EditRedirectRule(originalRedirectUrl, newRedirectUrl, destUrl, forwardChildpath == "true", redirectionStatusCode)
|
||||
err = redirectTable.EditRedirectRule(originalRedirectUrl, newRedirectUrl, destUrl, forwardChildpath == "true", redirectionStatusCode, requireExactMatch == "true")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
@@ -147,3 +159,30 @@ func handleToggleRedirectRegexpSupport(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Toggle redirection case sensitivity. Note that this affects all redirection rules
|
||||
func handleToggleRedirectCaseSensitivity(w http.ResponseWriter, r *http.Request) {
|
||||
enabled, err := utils.PostPara(r, "enable")
|
||||
if err != nil {
|
||||
//Return the current state of the case sensitivity
|
||||
js, _ := json.Marshal(redirectTable.CaseSensitive)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
return
|
||||
}
|
||||
|
||||
//Update the current case sensitivity rule enable state
|
||||
enableCaseSensitivity := strings.EqualFold(strings.TrimSpace(enabled), "true")
|
||||
redirectTable.CaseSensitive = enableCaseSensitivity
|
||||
err = sysdb.Write("redirect", "case_sensitive", enableCaseSensitivity)
|
||||
|
||||
if enableCaseSensitivity {
|
||||
SystemWideLogger.PrintAndLog("redirect", "Case sensitive redirect rule enabled", nil)
|
||||
} else {
|
||||
SystemWideLogger.PrintAndLog("redirect", "Case sensitive redirect rule disabled", nil)
|
||||
}
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to save settings")
|
||||
return
|
||||
}
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
12
src/start.go
12
src/start.go
@@ -50,14 +50,6 @@ import (
|
||||
Don't touch this function unless you know what you are doing
|
||||
*/
|
||||
|
||||
var (
|
||||
/*
|
||||
MDNS related
|
||||
*/
|
||||
previousmdnsScanResults = []*mdns.NetworkHost{}
|
||||
mdnsTickerStop chan bool
|
||||
)
|
||||
|
||||
func startupSequence() {
|
||||
//Start a system wide logger and log viewer
|
||||
l, err := logger.NewLogger(LOG_PREFIX, *path_logFile)
|
||||
@@ -149,7 +141,9 @@ func startupSequence() {
|
||||
db.NewTable("redirect")
|
||||
redirectAllowRegexp := false
|
||||
db.Read("redirect", "regex", &redirectAllowRegexp)
|
||||
redirectTable, err = redirection.NewRuleTable(CONF_REDIRECTION, redirectAllowRegexp, SystemWideLogger)
|
||||
redirectCaseSensitive := false
|
||||
db.Read("redirect", "case_sensitive", &redirectCaseSensitive)
|
||||
redirectTable, err = redirection.NewRuleTable(CONF_REDIRECTION, redirectAllowRegexp, redirectCaseSensitive, SystemWideLogger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<th>Redirection URL</th>
|
||||
<th>Destination URL</th>
|
||||
<th class="no-sort">Copy Pathname</th>
|
||||
<th class="no-sort">Require Exact Match</th>
|
||||
<th class="no-sort">Status Code</th>
|
||||
<th class="no-sort">Actions</th>
|
||||
</tr>
|
||||
@@ -44,6 +45,12 @@
|
||||
<small>Regular expression redirection check will noticeably slow down page load<br>
|
||||
Support <a href="https://yourbasic.org/golang/regexp-cheat-sheet/" target="_blank">Go style regex</a>. e.g. <code style="background-color: rgb(44, 44, 44); color: white">.\.redirect\.example\.com</code></small></label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="ui toggle checkbox" style="margin-top: 0.4em;">
|
||||
<input id="redirectCaseSensitive" type="checkbox">
|
||||
<label>Force Case Sensitive Check<br>
|
||||
<small>If enabled, URL matching will be case sensitive</small></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,12 +63,20 @@
|
||||
<div class="field">
|
||||
<label>Redirection URL (From)</label>
|
||||
<input type="text" id="rurl" name="redirection-url" placeholder="Redirection URL">
|
||||
<small><i class="ui circle info icon"></i> Any matching prefix of the request URL will be redirected to the destination URL, e.g. redirect.example.com</small>
|
||||
<small> Any matching prefix of the request URL will be redirected to the destination URL, e.g. redirect.example.com</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Destination URL (To)</label>
|
||||
<input type="text" name="destination-url" placeholder="Destination URL">
|
||||
<small><i class="ui circle info icon"></i> The target URL request being redirected to, e.g. dest.example.com/mysite/ or dest.example.com/script.php, <b>sometime you might need to add tailing slash (/) to your URL depending on your use cases</b></small>
|
||||
<small>The target URL request being redirected to, e.g. dest.example.com/mysite/ or dest.example.com/script.php, <b>sometime you might need to add tailing slash (/) to your URL depending on your use cases</b></small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
<input type="checkbox" name="require-exact-match" tabindex="0" class="hidden">
|
||||
<label>Require Exact Match</label>
|
||||
</div>
|
||||
<br>
|
||||
<small>If enabled, only exact URL matches will be redirected (no prefix matching)</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox">
|
||||
@@ -108,12 +123,14 @@
|
||||
document.getElementById("rurl").value = "";
|
||||
document.getElementsByName("destination-url")[0].value = "";
|
||||
document.getElementsByName("forward-childpath")[0].checked = true;
|
||||
document.getElementsByName("require-exact-match")[0].checked = false;
|
||||
}
|
||||
|
||||
function addRules(){
|
||||
let redirectUrl = document.querySelector('input[name="redirection-url"]').value;
|
||||
let destUrl = document.querySelector('input[name="destination-url"]').value;
|
||||
let forwardChildpath = document.querySelector('input[name="forward-childpath"]').checked;
|
||||
let requireExactMatch = document.querySelector('input[name="require-exact-match"]').checked;
|
||||
let redirectType = document.querySelector('input[name="redirect-type"]:checked').value;
|
||||
|
||||
$.cjax({
|
||||
@@ -123,6 +140,7 @@
|
||||
redirectUrl: redirectUrl,
|
||||
destUrl: destUrl,
|
||||
forwardChildpath: forwardChildpath,
|
||||
requireExactMatch: requireExactMatch,
|
||||
redirectType: parseInt(redirectType),
|
||||
},
|
||||
success: function(data){
|
||||
@@ -172,6 +190,7 @@
|
||||
<td><a href="${hrefURL}" target="_blank">${entry.RedirectURL}</a></td>
|
||||
<td>${entry.TargetURL}</td>
|
||||
<td>${entry.ForwardChildpath?"<i class='ui green checkmark icon'></i>":"<i class='ui red remove icon'></i>"}</td>
|
||||
<td>${entry.RequireExactMatch?"<i class='ui green checkmark icon'></i>":"<i class='ui red remove icon'></i>"}</td>
|
||||
<td>${entry.StatusCode==307?"Temporary Redirect (307)":"Moved Permanently (301)"}</td>
|
||||
<td>
|
||||
<button onclick="editRule(this);" payload="${encodedEntry}" title="Edit redirection rule" class="ui mini circular icon basic button redirectEditBtn"><i class="edit icon"></i></button>
|
||||
@@ -181,7 +200,7 @@
|
||||
});
|
||||
|
||||
if (data.length == 0){
|
||||
$("#redirectionRuleList").append(`<tr><td colspan="5"><i class="green check circle icon"></i> No redirection rule</td></tr>`);
|
||||
$("#redirectionRuleList").append(`<tr><td colspan="6"><i class="green check circle icon"></i> No redirection rule</td></tr>`);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -195,6 +214,7 @@
|
||||
let redirectUrl = payload.RedirectURL;
|
||||
let destUrl = payload.TargetURL;
|
||||
let forwardChildpath = payload.ForwardChildpath;
|
||||
let requireExactMatch = payload.RequireExactMatch || false;
|
||||
let statusCode = payload.StatusCode;
|
||||
|
||||
row.html(`
|
||||
@@ -209,6 +229,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td><div class="ui toggle checkbox"><input type="checkbox" ${forwardChildpath ? "checked" : ""} id="editForwardChildpath"><label></label></div></td>
|
||||
<td><div class="ui toggle checkbox"><input type="checkbox" ${requireExactMatch ? "checked" : ""} id="editRequireExactMatch"><label></label></div></td>
|
||||
<td>
|
||||
<div class="ui radio checkbox"><input type="radio" name="editStatusCode" value="307" ${statusCode == 307 ? "checked" : ""}><label>Temporary Redirect (307)</label></div><br>
|
||||
<div class="ui radio checkbox"><input type="radio" name="editStatusCode" value="301" ${statusCode == 301 ? "checked" : ""}><label>Moved Permanently (301)</label></div>
|
||||
@@ -227,6 +248,7 @@
|
||||
let redirectUrl = $("#editRedirectUrl").val();
|
||||
let destUrl = $("#editDestUrl").val();
|
||||
let forwardChildpath = $("#editForwardChildpath").is(":checked");
|
||||
let requireExactMatch = $("#editRequireExactMatch").is(":checked");
|
||||
let statusCode = parseInt($("input[name='editStatusCode']:checked").val());
|
||||
|
||||
$.cjax({
|
||||
@@ -237,6 +259,7 @@
|
||||
newRedirectUrl: redirectUrl,
|
||||
destUrl: destUrl,
|
||||
forwardChildpath: forwardChildpath,
|
||||
requireExactMatch: requireExactMatch,
|
||||
redirectType: statusCode,
|
||||
},
|
||||
success: function(data){
|
||||
@@ -279,6 +302,35 @@
|
||||
|
||||
initRegexpSupportToggle();
|
||||
|
||||
function initCaseSensitivityToggle(){
|
||||
$.get("/api/redirect/case_sensitive", function(data){
|
||||
//Set the checkbox initial state
|
||||
if (data == true){
|
||||
$("#redirectCaseSensitive").parent().checkbox("set checked");
|
||||
}else{
|
||||
$("#redirectCaseSensitive").parent().checkbox("set unchecked");
|
||||
}
|
||||
|
||||
//Bind event to the checkbox
|
||||
$("#redirectCaseSensitive").on("change", function(){
|
||||
$.cjax({
|
||||
url: "/api/redirect/case_sensitive",
|
||||
method: "POST",
|
||||
data: {"enable": $(this)[0].checked},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false);
|
||||
}else{
|
||||
msgbox("Case sensitive redirect setting updated", true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initCaseSensitivityToggle();
|
||||
|
||||
$("#rurl").on('change', (event) => {
|
||||
const value = event.target.value.trim().replace(/^(https?:\/\/)/, '');
|
||||
event.target.value = value;
|
||||
|
||||
Reference in New Issue
Block a user