mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-06 15:47:19 +02:00
Added regexp redirect support
This commit is contained in:
parent
05daeded37
commit
8db95dddc6
@ -40,7 +40,7 @@ General purpose request (reverse) proxy and forwarding tool for networking noobs
|
|||||||
For other systems or architectures, please see [Release](https://github.com/tobychui/zoraxy/releases/latest/)
|
For other systems or architectures, please see [Release](https://github.com/tobychui/zoraxy/releases/latest/)
|
||||||
|
|
||||||
## Build from Source
|
## Build from Source
|
||||||
Requires Go 1.20 or higher
|
Requires Go 1.22 or higher
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/tobychui/zoraxy
|
git clone https://github.com/tobychui/zoraxy
|
||||||
|
@ -85,6 +85,7 @@ func initAPIs() {
|
|||||||
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
|
||||||
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
|
||||||
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
|
||||||
|
authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport)
|
||||||
|
|
||||||
//Blacklist APIs
|
//Blacklist APIs
|
||||||
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
|
||||||
|
@ -27,7 +27,6 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
/*
|
/*
|
||||||
Special Routing Rules, bypass most of the limitations
|
Special Routing Rules, bypass most of the limitations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//Check if there are external routing rule matches.
|
//Check if there are external routing rule matches.
|
||||||
//If yes, route them via external rr
|
//If yes, route them via external rr
|
||||||
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
|
||||||
|
@ -2,19 +2,25 @@ package redirection
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RuleTable struct {
|
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
|
configPath string //The location where the redirection rules is stored
|
||||||
rules sync.Map //Store the redirection rules for this reverse proxy instance
|
rules sync.Map //Store the redirection rules for this reverse proxy instance
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedirectRules struct {
|
type RedirectRules struct {
|
||||||
@ -24,10 +30,11 @@ type RedirectRules struct {
|
|||||||
StatusCode int //Status Code for redirection
|
StatusCode int //Status Code for redirection
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRuleTable(configPath string) (*RuleTable, error) {
|
func NewRuleTable(configPath string, allowRegex bool) (*RuleTable, error) {
|
||||||
thisRuleTable := RuleTable{
|
thisRuleTable := RuleTable{
|
||||||
rules: sync.Map{},
|
rules: sync.Map{},
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
|
AllowRegex: allowRegex,
|
||||||
}
|
}
|
||||||
//Load all the rules from the config path
|
//Load all the rules from the config path
|
||||||
if !utils.FileExists(configPath) {
|
if !utils.FileExists(configPath) {
|
||||||
@ -77,7 +84,7 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
||||||
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
|
filename := utils.ReplaceSpecialCharacters(redirectURL) + ".json"
|
||||||
|
|
||||||
// Create the full file path by joining the t.configPath with the filename
|
// Create the full file path by joining the t.configPath with the filename
|
||||||
filepath := path.Join(t.configPath, filename)
|
filepath := path.Join(t.configPath, filename)
|
||||||
@ -105,11 +112,12 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP
|
|||||||
|
|
||||||
func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
|
func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
|
||||||
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
|
||||||
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
|
filename := utils.ReplaceSpecialCharacters(redirectURL) + ".json"
|
||||||
|
|
||||||
// Create the full file path by joining the t.configPath with the filename
|
// Create the full file path by joining the t.configPath with the filename
|
||||||
filepath := path.Join(t.configPath, filename)
|
filepath := path.Join(t.configPath, filename)
|
||||||
|
|
||||||
|
fmt.Println(redirectURL, filename, filepath)
|
||||||
// Check if the file exists
|
// Check if the file exists
|
||||||
if _, err := os.Stat(filepath); os.IsNotExist(err) {
|
if _, err := os.Stat(filepath); os.IsNotExist(err) {
|
||||||
return nil // File doesn't exist, nothing to delete
|
return nil // File doesn't exist, nothing to delete
|
||||||
@ -145,9 +153,23 @@ func (t *RuleTable) MatchRedirectRule(requestedURL string) *RedirectRules {
|
|||||||
// Iterate through all the keys in the rules map
|
// Iterate through all the keys in the rules map
|
||||||
var targetRedirectionRule *RedirectRules = nil
|
var targetRedirectionRule *RedirectRules = nil
|
||||||
var maxMatch int = 0
|
var maxMatch int = 0
|
||||||
|
|
||||||
t.rules.Range(func(key interface{}, value interface{}) bool {
|
t.rules.Range(func(key interface{}, value interface{}) bool {
|
||||||
// Check if the requested URL starts with the key as a prefix
|
// Check if the requested URL starts with the key as a prefix
|
||||||
|
if t.AllowRegex {
|
||||||
|
//Regexp matching rule
|
||||||
|
matched, err := regexp.MatchString(key.(string), 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//Default: prefix matching redirect
|
||||||
if strings.HasPrefix(requestedURL, key.(string)) {
|
if strings.HasPrefix(requestedURL, key.(string)) {
|
||||||
// This request URL matched the domain
|
// This request URL matched the domain
|
||||||
if len(key.(string)) > maxMatch {
|
if len(key.(string)) > maxMatch {
|
||||||
@ -155,8 +177,23 @@ func (t *RuleTable) MatchRedirectRule(requestedURL string) *RedirectRules {
|
|||||||
targetRedirectionRule = value.(*RedirectRules)
|
targetRedirectionRule = value.(*RedirectRules)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
return targetRedirectionRule
|
return targetRedirectionRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the message to log file, use STDOUT if logger not set
|
||||||
|
func (t *RuleTable) log(message string, err error) {
|
||||||
|
if t.Logger == nil {
|
||||||
|
if err == nil {
|
||||||
|
log.Println("[Redirect] " + message)
|
||||||
|
} else {
|
||||||
|
log.Println("[Redirect] " + message + ": " + err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Logger.PrintAndLog("Redirect", message, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "strconv"
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
func StringToInt64(number string) (int64, error) {
|
func StringToInt64(number string) (int64, error) {
|
||||||
i, err := strconv.ParseInt(number, 10, 64)
|
i, err := strconv.ParseInt(number, 10, 64)
|
||||||
@ -14,3 +17,36 @@ func Int64ToString(number int64) string {
|
|||||||
convedNumber := strconv.FormatInt(number, 10)
|
convedNumber := strconv.FormatInt(number, 10)
|
||||||
return convedNumber
|
return convedNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReplaceSpecialCharacters(filename string) string {
|
||||||
|
replacements := map[string]string{
|
||||||
|
"#": "%pound%",
|
||||||
|
"&": "%amp%",
|
||||||
|
"{": "%left_cur%",
|
||||||
|
"}": "%right_cur%",
|
||||||
|
"\\": "%backslash%",
|
||||||
|
"<": "%left_ang%",
|
||||||
|
">": "%right_ang%",
|
||||||
|
"*": "%aster%",
|
||||||
|
"?": "%quest%",
|
||||||
|
" ": "%space%",
|
||||||
|
"$": "%dollar%",
|
||||||
|
"!": "%exclan%",
|
||||||
|
"'": "%sin_q%",
|
||||||
|
"\"": "%dou_q%",
|
||||||
|
":": "%colon%",
|
||||||
|
"@": "%at%",
|
||||||
|
"+": "%plus%",
|
||||||
|
"`": "%backtick%",
|
||||||
|
"|": "%pipe%",
|
||||||
|
"=": "%equal%",
|
||||||
|
".": "_",
|
||||||
|
"/": "-",
|
||||||
|
}
|
||||||
|
|
||||||
|
for char, replacement := range replacements {
|
||||||
|
filename = strings.ReplaceAll(filename, char, replacement)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
@ -15,12 +16,14 @@ import (
|
|||||||
related to redirection function in the reverse proxy
|
related to redirection function in the reverse proxy
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Handle request for listing all stored redirection rules
|
||||||
func handleListRedirectionRules(w http.ResponseWriter, r *http.Request) {
|
func handleListRedirectionRules(w http.ResponseWriter, r *http.Request) {
|
||||||
rules := redirectTable.GetAllRedirectRules()
|
rules := redirectTable.GetAllRedirectRules()
|
||||||
js, _ := json.Marshal(rules)
|
js, _ := json.Marshal(rules)
|
||||||
utils.SendJSONResponse(w, string(js))
|
utils.SendJSONResponse(w, string(js))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle request for adding new redirection rule
|
||||||
func handleAddRedirectionRule(w http.ResponseWriter, r *http.Request) {
|
func handleAddRedirectionRule(w http.ResponseWriter, r *http.Request) {
|
||||||
redirectUrl, err := utils.PostPara(r, "redirectUrl")
|
redirectUrl, err := utils.PostPara(r, "redirectUrl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -58,6 +61,7 @@ func handleAddRedirectionRule(w http.ResponseWriter, r *http.Request) {
|
|||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle remove of a given redirection rule
|
||||||
func handleDeleteRedirectionRule(w http.ResponseWriter, r *http.Request) {
|
func handleDeleteRedirectionRule(w http.ResponseWriter, r *http.Request) {
|
||||||
redirectUrl, err := utils.PostPara(r, "redirectUrl")
|
redirectUrl, err := utils.PostPara(r, "redirectUrl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -73,3 +77,30 @@ func handleDeleteRedirectionRule(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle redirection regex support. Note that this cost another O(n) time complexity to each page load
|
||||||
|
func handleToggleRedirectRegexpSupport(w http.ResponseWriter, r *http.Request) {
|
||||||
|
enabled, err := utils.PostPara(r, "enable")
|
||||||
|
if err != nil {
|
||||||
|
//Return the current state of the regex support
|
||||||
|
js, _ := json.Marshal(redirectTable.AllowRegex)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update the current regex support rule enable state
|
||||||
|
enableRegexSupport := strings.EqualFold(strings.TrimSpace(enabled), "true")
|
||||||
|
redirectTable.AllowRegex = enableRegexSupport
|
||||||
|
err = sysdb.Write("Redirect", "regex", enableRegexSupport)
|
||||||
|
|
||||||
|
if enableRegexSupport {
|
||||||
|
SystemWideLogger.PrintAndLog("redirect", "Regex redirect rule enabled", nil)
|
||||||
|
} else {
|
||||||
|
SystemWideLogger.PrintAndLog("redirect", "Regex redirect rule disabled", nil)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "unable to save settings")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
@ -73,10 +73,14 @@ func startupSequence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Create a redirection rule table
|
//Create a redirection rule table
|
||||||
redirectTable, err = redirection.NewRuleTable("./conf/redirect")
|
db.NewTable("redirect")
|
||||||
|
redirectAllowRegexp := false
|
||||||
|
db.Read("redirect", "regex", &redirectAllowRegexp)
|
||||||
|
redirectTable, err = redirection.NewRuleTable("./conf/redirect", redirectAllowRegexp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
redirectTable.Logger = SystemWideLogger
|
||||||
|
|
||||||
//Create a geodb store
|
//Create a geodb store
|
||||||
geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{
|
geodbStore, err = geodb.NewGeoDb(sysdb, &geodb.StoreOptions{
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<h2>Redirection Rules</h2>
|
<h2>Redirection Rules</h2>
|
||||||
<p>Add exception case for redirecting any matching URLs</p>
|
<p>Add exception case for redirecting any matching URLs</p>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Current list of redirection rules-->
|
||||||
<div style="width: 100%; overflow-x: auto;">
|
<div style="width: 100%; overflow-x: auto;">
|
||||||
<table class="ui sortable unstackable celled table" >
|
<table class="ui sortable unstackable celled table" >
|
||||||
<thead>
|
<thead>
|
||||||
@ -28,6 +29,27 @@
|
|||||||
<div class="ui green message" id="delRuleSucc" style="display:none;">
|
<div class="ui green message" id="delRuleSucc" style="display:none;">
|
||||||
<i class="ui green checkmark icon"></i> Redirection Rule Deleted
|
<i class="ui green checkmark icon"></i> Redirection Rule Deleted
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Options -->
|
||||||
|
<div class="ui basic segment" style="background-color: #f7f7f7; border-radius: 1em;">
|
||||||
|
<div class="ui accordion advanceSettings">
|
||||||
|
<div class="title">
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
Advance Settings
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="ui basic segment">
|
||||||
|
<div class="ui toggle checkbox">
|
||||||
|
<input id="redirectRegex" type="checkbox">
|
||||||
|
<label>Enable Regular Expression Support<br>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add New Redirection Rules -->
|
||||||
<div class="ui divider"></div>
|
<div class="ui divider"></div>
|
||||||
<h4>Add Redirection Rule</h4>
|
<h4>Add Redirection Rule</h4>
|
||||||
<div class="ui form">
|
<div class="ui form">
|
||||||
@ -76,12 +98,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Redirection functions
|
Redirection functions
|
||||||
*/
|
*/
|
||||||
$(".checkbox").checkbox();
|
|
||||||
|
|
||||||
|
$(".checkbox").checkbox();
|
||||||
|
$(".advanceSettings").accordion();
|
||||||
function resetForm() {
|
function resetForm() {
|
||||||
document.getElementById("rurl").value = "";
|
document.getElementById("rurl").value = "";
|
||||||
document.getElementsByName("destination-url")[0].value = "";
|
document.getElementsByName("destination-url")[0].value = "";
|
||||||
@ -158,6 +180,34 @@
|
|||||||
}
|
}
|
||||||
initRedirectionRuleList();
|
initRedirectionRuleList();
|
||||||
|
|
||||||
|
function initRegexpSupportToggle(){
|
||||||
|
$.get("/api/redirect/regex", function(data){
|
||||||
|
//Set the checkbox initial state
|
||||||
|
if (data == true){
|
||||||
|
$("#redirectRegex").parent().checkbox("set checked");
|
||||||
|
}else{
|
||||||
|
$("#redirectRegex").parent().checkbox("set unchecked");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Bind event to the checkbox
|
||||||
|
$("#redirectRegex").on("change", function(){
|
||||||
|
$.ajax({
|
||||||
|
url: "/api/redirect/regex",
|
||||||
|
data: {"enable": $(this)[0].checked},
|
||||||
|
success: function(data){
|
||||||
|
if (data.error != undefined){
|
||||||
|
msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
msgbox("Regex redirect setting updated", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initRegexpSupportToggle();
|
||||||
|
|
||||||
$("#rurl").on('change', (event) => {
|
$("#rurl").on('change', (event) => {
|
||||||
const value = event.target.value.trim().replace(/^(https?:\/\/)/, '');
|
const value = event.target.value.trim().replace(/^(https?:\/\/)/, '');
|
||||||
event.target.value = value;
|
event.target.value = value;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user