Removed alpha prototype source

This commit is contained in:
Toby Chui 2023-05-04 20:42:35 +08:00
parent 2c586aee32
commit a1d779a0ce
275 changed files with 0 additions and 150920 deletions

View File

@ -1,54 +0,0 @@
# PLATFORMS := darwin/amd64 darwin/arm64 freebsd/amd64 linux/386 linux/amd64 linux/arm linux/arm64 linux/mipsle windows/386 windows/amd64 windows/arm windows/arm64
PLATFORMS := darwin/amd64 darwin/arm64 linux/amd64 linux/arm linux/arm64 linux/mipsle linux/riscv64 windows/amd64 windows/arm64
temp = $(subst /, ,$@)
os = $(word 1, $(temp))
arch = $(word 2, $(temp))
all: web.tar.gz $(PLATFORMS) fixwindows zoraxy_file_checksum.sha1
binary: $(PLATFORMS)
hash: zoraxy_file_checksum.sha1
web: web.tar.gz
clean:
rm -f zoraxy_*_*
rm -f web.tar.gz
$(PLATFORMS):
@echo "Building $(os)/$(arch)"
GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) GOARM=6 go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath
fixwindows:
-mv ./dist/zoraxy_windows_amd64 ./dist/zoraxy_windows_amd64.exe
-mv ./dist/zoraxy_windows_arm64 ./dist/zoraxy_windows_arm64.exe
web.tar.gz:
@echo "Removing old build resources, if exists"
-rm -rf ./dist/
-mkdir ./dist/
@echo "Moving subfolders to build folder"
-cp -r ./web ./dist/web/
-cp -r ./system ./dist/system/
@ echo "Remove sensitive information"
-rm -rf ./dist/certs/
-rm -rf ./dist/conf/
-rm -rf ./dist/rules/
@echo "Creating tarball for all required files"
-rm ./dist/web.tar.gz
-cd ./dist/ && tar -czf ./web.tar.gz system/ web/
@echo "Clearing up unzipped folder structures"
-rm -rf "./dist/web"
-rm -rf "./dist/system"
zoraxy_file_checksum.sha1:
@echo "Generating the checksum, if sha1sum installed"
-sha1sum ./dist/web.tar.gz > ./dist/zoraxy_file_checksum.sha1

View File

@ -1,50 +0,0 @@
# ReverseProxy
Reverse proxy subservice for ArozOS
### Install
Git clone this inside the subservice folder of ArozOS, build it and start it up in ArozOS subservice menu.
You can also use it separately as a really basic reverse proxy server that server website based on domain / virtual directories. You can also add another load balancer in front of multiple reverse proxy server nodes if it is necessary in your case.
### Build
Requirement: Go 1.16 or above
```
cd ~/arozos/subservices
git clone {this_repo}
cd ReverseProxy
./build.sh
```
To start the module, make sure .disabled file does not exists and start arozos system. You should be able to see a new module named "ReverseProxy" pop up in the start menu.
### Usage
#### Proxy Modes
There are two mode in the ReverseProxy Subservice
1. vdir mode (Virtual Dirctories)
2. subd mode (Subdomain Proxying Mode)
Vdir mode proxy web request based on the virtual directories given in the request URL. For example, when configured to redirect /example to example.com, any visits to {your_domain}/example will be proxied to example.com.
Subd mode proxy web request based on sub-domain exists in the request URL. For example, when configured to redirect example.localhost to example.com, any visits that includes example.localhost (e.g. example.localhost/page1) will be proxied to example.com (e.g. example.com/page1)
#### Root Proxy
Root proxy is the main proxy destination where if all proxy root name did not match, the request will be proxied to this request. If you are working with ArozOS system in default configuration, you can set this to localhost:8080 for any unknown request to be handled by the host ArozOS system

View File

@ -1,154 +0,0 @@
package main
import (
"encoding/json"
"net/http"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/utils"
)
/*
API.go
This file contains all the API called by the web management interface
*/
func initAPIs() {
requireAuth := !(*noauth || handler.IsUsingExternalPermissionManager())
authRouter := auth.NewManagedHTTPRouter(auth.RouterOption{
AuthAgent: authAgent,
RequireAuth: requireAuth,
DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
},
})
//Register the standard web services urls
fs := http.FileServer(http.Dir("./web"))
if requireAuth {
//Add a layer of middleware for auth control
authHandler := AuthFsHandler(fs)
http.Handle("/", authHandler)
} else {
http.Handle("/", fs)
}
//Authentication APIs
registerAuthAPIs(requireAuth)
//Reverse proxy
authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff)
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck)
//TLS / SSL config
authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy)
authRouter.HandleFunc("/api/cert/upload", handleCertUpload)
authRouter.HandleFunc("/api/cert/list", handleListCertificate)
authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck)
authRouter.HandleFunc("/api/cert/delete", handleCertRemove)
//Redirection config
authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules)
authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule)
authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule)
//Blacklist APIs
authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted)
authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd)
authRouter.HandleFunc("/api/blacklist/country/remove", handleCountryBlacklistRemove)
authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd)
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
//Statistic & uptime monitoring API
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
//Upnp
authRouter.HandleFunc("/api/upnp/discover", handleUpnpDiscover)
//If you got APIs to add, append them here
}
//Function to renders Auth related APIs
func registerAuthAPIs(requireAuth bool) {
//Auth APIs
http.HandleFunc("/api/auth/login", authAgent.HandleLogin)
http.HandleFunc("/api/auth/logout", authAgent.HandleLogout)
http.HandleFunc("/api/auth/checkLogin", func(w http.ResponseWriter, r *http.Request) {
if requireAuth {
authAgent.CheckLogin(w, r)
} else {
utils.SendJSONResponse(w, "true")
}
})
http.HandleFunc("/api/auth/username", func(w http.ResponseWriter, r *http.Request) {
username, err := authAgent.GetUserName(w, r)
if err != nil {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
js, _ := json.Marshal(username)
utils.SendJSONResponse(w, string(js))
})
http.HandleFunc("/api/auth/userCount", func(w http.ResponseWriter, r *http.Request) {
uc := authAgent.GetUserCounts()
js, _ := json.Marshal(uc)
utils.SendJSONResponse(w, string(js))
})
http.HandleFunc("/api/auth/register", func(w http.ResponseWriter, r *http.Request) {
if authAgent.GetUserCounts() == 0 {
//Allow register root admin
authAgent.HandleRegisterWithoutEmail(w, r, func(username, reserved string) {
})
} else {
//This function is disabled
utils.SendErrorResponse(w, "Root management account already exists")
}
})
http.HandleFunc("/api/auth/changePassword", func(w http.ResponseWriter, r *http.Request) {
username, err := authAgent.GetUserName(w, r)
if err != nil {
http.Error(w, "401 - Unauthorized", http.StatusUnauthorized)
return
}
oldPassword, err := utils.PostPara(r, "oldPassword")
if err != nil {
utils.SendErrorResponse(w, "empty current password")
return
}
newPassword, err := utils.PostPara(r, "newPassword")
if err != nil {
utils.SendErrorResponse(w, "empty new password")
return
}
confirmPassword, _ := utils.PostPara(r, "confirmPassword")
if newPassword != confirmPassword {
utils.SendErrorResponse(w, "confirm password not match")
return
}
//Check if the old password correct
oldPasswordCorrect, _ := authAgent.ValidateUsernameAndPasswordWithReason(username, oldPassword)
if !oldPasswordCorrect {
utils.SendErrorResponse(w, "Invalid current password given")
return
}
//Change the password of the root user
authAgent.UnregisterUser(username)
authAgent.CreateUserAccount(username, newPassword, "")
})
}

View File

@ -1,102 +0,0 @@
package main
import (
"encoding/json"
"net/http"
"imuslab.com/zoraxy/mod/utils"
)
/*
blacklist.go
This script file is added to extend the
reverse proxy function to include
banning a specific IP address or country code
*/
//List a of blacklisted ip address or country code
func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
bltype, err := utils.GetPara(r, "type")
if err != nil {
bltype = "country"
}
resulst := []string{}
if bltype == "country" {
resulst = geodbStore.GetAllBlacklistedCountryCode()
} else if bltype == "ip" {
resulst = geodbStore.GetAllBlacklistedIp()
}
js, _ := json.Marshal(resulst)
utils.SendJSONResponse(w, string(js))
}
func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
countryCode, err := utils.PostPara(r, "cc")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty country code")
return
}
geodbStore.AddCountryCodeToBlackList(countryCode)
utils.SendOK(w)
}
func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
countryCode, err := utils.PostPara(r, "cc")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty country code")
return
}
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
utils.SendOK(w)
}
func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
ipAddr, err := utils.PostPara(r, "ip")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty ip address")
return
}
geodbStore.AddIPToBlackList(ipAddr)
}
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
ipAddr, err := utils.PostPara(r, "ip")
if err != nil {
utils.SendErrorResponse(w, "invalid or empty ip address")
return
}
geodbStore.RemoveIPFromBlackList(ipAddr)
utils.SendOK(w)
}
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
enable, err := utils.PostPara(r, "enable")
if err != nil {
//Return the current enabled state
currentEnabled := geodbStore.Enabled
js, _ := json.Marshal(currentEnabled)
utils.SendJSONResponse(w, string(js))
} else {
if enable == "true" {
geodbStore.ToggleBlacklist(true)
} else if enable == "false" {
geodbStore.ToggleBlacklist(false)
} else {
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
return
}
utils.SendOK(w)
}
}

View File

@ -1,189 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"imuslab.com/zoraxy/mod/utils"
)
// Check if the default certificates is correctly setup
func handleDefaultCertCheck(w http.ResponseWriter, r *http.Request) {
type CheckResult struct {
DefaultPubExists bool
DefaultPriExists bool
}
pub, pri := tlsCertManager.DefaultCertExistsSep()
js, _ := json.Marshal(CheckResult{
pub,
pri,
})
utils.SendJSONResponse(w, string(js))
}
// Return a list of domains where the certificates covers
func handleListCertificate(w http.ResponseWriter, r *http.Request) {
filenames, err := tlsCertManager.ListCertDomains()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
showDate, _ := utils.GetPara(r, "date")
if showDate == "true" {
type CertInfo struct {
Domain string
LastModifiedDate string
}
results := []*CertInfo{}
for _, filename := range filenames {
fileInfo, err := os.Stat(filepath.Join(tlsCertManager.CertStore, filename+".crt"))
if err != nil {
utils.SendErrorResponse(w, "invalid domain certificate discovered: "+filename)
return
}
modifiedTime := fileInfo.ModTime().Format("2006-01-02 15:04:05")
thisCertInfo := CertInfo{
Domain: filename,
LastModifiedDate: modifiedTime,
}
results = append(results, &thisCertInfo)
}
js, _ := json.Marshal(results)
w.Header().Set("Content-Type", "application/json")
w.Write(js)
} else {
response, err := json.Marshal(filenames)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(response)
}
}
// Handle front-end toggling TLS mode
func handleToggleTLSProxy(w http.ResponseWriter, r *http.Request) {
currentTlsSetting := false
if sysdb.KeyExists("settings", "usetls") {
sysdb.Read("settings", "usetls", &currentTlsSetting)
}
newState, err := utils.PostPara(r, "set")
if err != nil {
//No setting. Get the current status
js, _ := json.Marshal(currentTlsSetting)
utils.SendJSONResponse(w, string(js))
} else {
if newState == "true" {
sysdb.Write("settings", "usetls", true)
log.Println("Enabling TLS mode on reverse proxy")
dynamicProxyRouter.UpdateTLSSetting(true)
} else if newState == "false" {
sysdb.Write("settings", "usetls", false)
log.Println("Disabling TLS mode on reverse proxy")
dynamicProxyRouter.UpdateTLSSetting(false)
} else {
utils.SendErrorResponse(w, "invalid state given. Only support true or false")
return
}
utils.SendOK(w)
}
}
// Handle upload of the certificate
func handleCertUpload(w http.ResponseWriter, r *http.Request) {
// check if request method is POST
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// get the key type
keytype, err := utils.GetPara(r, "ktype")
overWriteFilename := ""
if err != nil {
http.Error(w, "Not defined key type (pub / pri)", http.StatusBadRequest)
return
}
// get the domain
domain, err := utils.GetPara(r, "domain")
if err != nil {
//Assume localhost
domain = "default"
}
if keytype == "pub" {
overWriteFilename = domain + ".crt"
} else if keytype == "pri" {
overWriteFilename = domain + ".key"
} else {
http.Error(w, "Not supported keytype: "+keytype, http.StatusBadRequest)
return
}
// parse multipart form data
err = r.ParseMultipartForm(10 << 20) // 10 MB
if err != nil {
http.Error(w, "Failed to parse form data", http.StatusBadRequest)
return
}
// get file from form data
file, _, err := r.FormFile("file")
if err != nil {
http.Error(w, "Failed to get file", http.StatusBadRequest)
return
}
defer file.Close()
// create file in upload directory
os.MkdirAll("./certs", 0775)
f, err := os.Create(filepath.Join("./certs", overWriteFilename))
if err != nil {
http.Error(w, "Failed to create file", http.StatusInternalServerError)
return
}
defer f.Close()
// copy file contents to destination file
_, err = io.Copy(f, file)
if err != nil {
http.Error(w, "Failed to save file", http.StatusInternalServerError)
return
}
// send response
fmt.Fprintln(w, "File upload successful!")
}
// Handle cert remove
func handleCertRemove(w http.ResponseWriter, r *http.Request) {
domain, err := utils.PostPara(r, "domain")
if err != nil {
utils.SendErrorResponse(w, "invalid domain given")
return
}
err = tlsCertManager.RemoveCert(domain)
if err != nil {
utils.SendErrorResponse(w, err.Error())
}
}

View File

@ -1,79 +0,0 @@
package main
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"imuslab.com/zoraxy/mod/utils"
)
type Record struct {
ProxyType string
Rootname string
ProxyTarget string
UseTLS bool
}
func SaveReverseProxyConfig(ptype string, rootname string, proxyTarget string, useTLS bool) error {
os.MkdirAll("conf", 0775)
filename := getFilenameFromRootName(rootname)
//Generate record
thisRecord := Record{
ProxyType: ptype,
Rootname: rootname,
ProxyTarget: proxyTarget,
UseTLS: useTLS,
}
//Write to file
js, _ := json.MarshalIndent(thisRecord, "", " ")
return ioutil.WriteFile(filepath.Join("conf", filename), js, 0775)
}
func RemoveReverseProxyConfig(rootname string) error {
filename := getFilenameFromRootName(rootname)
removePendingFile := strings.ReplaceAll(filepath.Join("conf", filename), "\\", "/")
log.Println("Config Removed: ", removePendingFile)
if utils.FileExists(removePendingFile) {
err := os.Remove(removePendingFile)
if err != nil {
log.Println(err.Error())
return err
}
}
//File already gone
return nil
}
// Return ptype, rootname and proxyTarget, error if any
func LoadReverseProxyConfig(filename string) (*Record, error) {
thisRecord := Record{}
configContent, err := ioutil.ReadFile(filename)
if err != nil {
return &thisRecord, err
}
//Unmarshal the content into config
err = json.Unmarshal(configContent, &thisRecord)
if err != nil {
return &thisRecord, err
}
//Return it
return &thisRecord, nil
}
func getFilenameFromRootName(rootname string) string {
//Generate a filename for this rootname
filename := strings.ReplaceAll(rootname, ".", "_")
filename = strings.ReplaceAll(filename, "/", "-")
filename = filename + ".config"
return filename
}

View File

@ -1,39 +0,0 @@
package main
import (
"net"
"net/http"
"strings"
"github.com/oschwald/geoip2-golang"
)
func getCountryCodeFromRequest(r *http.Request) string {
countryCode := ""
// Get the IP address of the user from the request headers
ipAddress := r.Header.Get("X-Forwarded-For")
if ipAddress == "" {
ipAddress = strings.Split(r.RemoteAddr, ":")[0]
}
// Open the GeoIP database
db, err := geoip2.Open("./system/GeoIP2-Country.mmdb")
if err != nil {
// Handle the error
return countryCode
}
defer db.Close()
// Look up the country code for the IP address
record, err := db.Country(net.ParseIP(ipAddress))
if err != nil {
// Handle the error
return countryCode
}
// Get the ISO country code from the record
countryCode = record.Country.IsoCode
return countryCode
}

View File

@ -1,12 +0,0 @@
module imuslab.com/zoraxy
go 1.16
require (
github.com/boltdb/bolt v1.3.1
github.com/gorilla/sessions v1.2.1
github.com/gorilla/websocket v1.4.2
github.com/oschwald/geoip2-golang v1.8.0
gitlab.com/NebulousLabs/go-upnp v0.0.0-20211002182029-11da932010b6
golang.org/x/sys v0.6.0 // indirect
)

View File

@ -1,46 +0,0 @@
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40 h1:dizWJqTWjwyD8KGcMOwgrkqu1JIkofYgKkmDeNE7oAs=
gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40/go.mod h1:rOnSnoRyxMI3fe/7KIbVcsHRGxe30OONv8dEgo+vCfA=
gitlab.com/NebulousLabs/go-upnp v0.0.0-20211002182029-11da932010b6 h1:WKij6HF8ECp9E7K0E44dew9NrRDGiNR5u4EFsXnJUx4=
gitlab.com/NebulousLabs/go-upnp v0.0.0-20211002182029-11da932010b6/go.mod h1:vhrHTGDh4YR7wK8Z+kRJ+x8SF/6RUM3Vb64Si5FD0L8=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1 h1:4qWs8cYYH6PoEFy4dfhDFgoMGkwAcETd+MmPdCPMzUc=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,97 +0,0 @@
package main
import (
"encoding/json"
"net/http"
"imuslab.com/zoraxy/mod/dynamicproxy"
"imuslab.com/zoraxy/mod/uptime"
"imuslab.com/zoraxy/mod/utils"
)
/*
Statistic Summary
*/
//Handle conversion of statistic daily summary to country summary
func HandleCountryDistrSummary(w http.ResponseWriter, r *http.Request) {
requestClientCountry := map[string]int{}
statisticCollector.DailySummary.RequestClientIp.Range(func(key, value interface{}) bool {
//Get this client country of original
clientIp := key.(string)
//requestCount := value.(int)
ci, err := geodbStore.ResolveCountryCodeFromIP(clientIp)
if err != nil {
return true
}
isoCode := ci.CountryIsoCode
if isoCode == "" {
//local or reserved addr
isoCode = "local"
}
uc, ok := requestClientCountry[isoCode]
if !ok {
//Create the counter
requestClientCountry[isoCode] = 1
} else {
requestClientCountry[isoCode] = uc + 1
}
return true
})
js, _ := json.Marshal(requestClientCountry)
utils.SendJSONResponse(w, string(js))
}
/*
Up Time Monitor
*/
//Generate uptime monitor targets from reverse proxy rules
func GetUptimeTargetsFromReverseProxyRules(dp *dynamicproxy.Router) []*uptime.Target {
subds := dp.GetSDProxyEndpointsAsMap()
vdirs := dp.GetVDProxyEndpointsAsMap()
UptimeTargets := []*uptime.Target{}
for subd, target := range subds {
url := "http://" + target.Domain
protocol := "http"
if target.RequireTLS {
url = "https://" + target.Domain
protocol = "https"
}
UptimeTargets = append(UptimeTargets, &uptime.Target{
ID: subd,
Name: subd,
URL: url,
Protocol: protocol,
})
}
for vdir, target := range vdirs {
url := "http://" + target.Domain
protocol := "http"
if target.RequireTLS {
url = "https://" + target.Domain
protocol = "https"
}
UptimeTargets = append(UptimeTargets, &uptime.Target{
ID: vdir,
Name: "*" + vdir,
URL: url,
Protocol: protocol,
})
}
return UptimeTargets
}
//Handle rendering up time monitor data
func HandleUptimeMonitorListing(w http.ResponseWriter, r *http.Request) {
if uptimeMonitor != nil {
uptimeMonitor.HandleUptimeLogRead(w, r)
} else {
http.Error(w, "500 - Internal Server Error", http.StatusInternalServerError)
return
}
}

View File

@ -1,153 +0,0 @@
package main
import (
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"imuslab.com/zoraxy/mod/aroz"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/tlscert"
"imuslab.com/zoraxy/mod/upnp"
"imuslab.com/zoraxy/mod/uptime"
)
//General flags
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
var showver = flag.Bool("version", false, "Show version of this server")
var (
name = "Zoraxy"
version = "2.11"
handler *aroz.ArozHandler //Handle arozos managed permission system
sysdb *database.Database //System database
authAgent *auth.AuthAgent //Authentication agent
tlsCertManager *tlscert.Manager //TLS / SSL management
redirectTable *redirection.RuleTable //Handle special redirection rule sets
geodbStore *geodb.Store //GeoIP database
statisticCollector *statistic.Collector //Collecting statistic from visitors
upnpClient *upnp.UPnPClient //UPnP Client for poking holes
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
)
// Kill signal handler. Do something before the system the core terminate.
func SetupCloseHandler() {
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
log.Println("\r- Shutting down " + name)
geodbStore.Close()
statisticCollector.Close()
//Close database, final
sysdb.Close()
os.Exit(0)
}()
}
func main() {
//Start the aoModule pipeline (which will parse the flags as well). Pass in the module launch information
handler = aroz.HandleFlagParse(aroz.ServiceInfo{
Name: name,
Desc: "Dynamic Reverse Proxy Server",
Group: "Network",
IconPath: "reverseproxy/img/small_icon.png",
Version: version,
StartDir: "reverseproxy/index.html",
SupportFW: true,
LaunchFWDir: "reverseproxy/index.html",
SupportEmb: false,
InitFWSize: []int{1080, 580},
})
if *showver {
fmt.Println(name + " - Version " + version)
os.Exit(0)
}
SetupCloseHandler()
//Create database
db, err := database.NewDatabase("sys.db", false)
if err != nil {
log.Fatal(err)
}
sysdb = db
//Create tables for the database
sysdb.NewTable("settings")
//Create an auth agent
sessionKey, err := auth.GetSessionKey(sysdb)
if err != nil {
log.Fatal(err)
}
authAgent = auth.NewAuthenticationAgent(name, []byte(sessionKey), sysdb, true, func(w http.ResponseWriter, r *http.Request) {
//Not logged in. Redirecting to login page
http.Redirect(w, r, "/login.html", http.StatusTemporaryRedirect)
})
//Create a TLS certificate manager
tlsCertManager, err = tlscert.NewManager("./certs")
if err != nil {
panic(err)
}
//Create a redirection rule table
redirectTable, err = redirection.NewRuleTable("./rules")
if err != nil {
panic(err)
}
//Create a geodb store
geodbStore, err = geodb.NewGeoDb(sysdb, "./system/GeoLite2-Country.mmdb")
if err != nil {
panic(err)
}
//Create a statistic collector
statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
Database: sysdb,
})
if err != nil {
panic(err)
}
if err != nil {
panic(err)
}
//Create a upnp client
err = initUpnp()
if err != nil {
panic(err)
}
//Initiate management interface APIs
initAPIs()
//Start the reverse proxy server in go routine
go func() {
ReverseProxtInit()
}()
time.Sleep(500 * time.Millisecond)
//Any log println will be shown in the core system via STDOUT redirection. But not STDIN.
log.Println("ReverseProxy started. Visit control panel at http://localhost" + handler.Port)
err = http.ListenAndServe(handler.Port, nil)
if err != nil {
log.Fatal(err)
}
}

View File

@ -1,76 +0,0 @@
package aroz
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"net/url"
"os"
)
//To be used with arozos system
type ArozHandler struct {
Port string
restfulEndpoint string
}
//Information required for registering this subservice to arozos
type ServiceInfo struct {
Name string //Name of this module. e.g. "Audio"
Desc string //Description for this module
Group string //Group of the module, e.g. "system" / "media" etc
IconPath string //Module icon image path e.g. "Audio/img/function_icon.png"
Version string //Version of the module. Format: [0-9]*.[0-9][0-9].[0-9]
StartDir string //Default starting dir, e.g. "Audio/index.html"
SupportFW bool //Support floatWindow. If yes, floatWindow dir will be loaded
LaunchFWDir string //This link will be launched instead of 'StartDir' if fw mode
SupportEmb bool //Support embedded mode
LaunchEmb string //This link will be launched instead of StartDir / Fw if a file is opened with this module
InitFWSize []int //Floatwindow init size. [0] => Width, [1] => Height
InitEmbSize []int //Embedded mode init size. [0] => Width, [1] => Height
SupportedExt []string //Supported File Extensions. e.g. ".mp3", ".flac", ".wav"
}
//This function will request the required flag from the startup paramters and parse it to the need of the arozos.
func HandleFlagParse(info ServiceInfo) *ArozHandler {
var infoRequestMode = flag.Bool("info", false, "Show information about this program in JSON")
var port = flag.String("port", ":8000", "Management web interface listening port")
var restful = flag.String("rpt", "", "Reserved")
//Parse the flags
flag.Parse()
if *infoRequestMode {
//Information request mode
jsonString, _ := json.MarshalIndent(info, "", " ")
fmt.Println(string(jsonString))
os.Exit(0)
}
return &ArozHandler{
Port: *port,
restfulEndpoint: *restful,
}
}
//Get the username and resources access token from the request, return username, token
func (a *ArozHandler) GetUserInfoFromRequest(w http.ResponseWriter, r *http.Request) (string, string) {
username := r.Header.Get("aouser")
token := r.Header.Get("aotoken")
return username, token
}
func (a *ArozHandler) IsUsingExternalPermissionManager() bool {
return !(a.restfulEndpoint == "")
}
//Request gateway interface for advance permission sandbox control
func (a *ArozHandler) RequestGatewayInterface(token string, script string) (*http.Response, error) {
resp, err := http.PostForm(a.restfulEndpoint,
url.Values{"token": {token}, "script": {script}})
if err != nil {
// handle error
return nil, err
}
return resp, nil
}

Binary file not shown.

View File

@ -1,478 +0,0 @@
package auth
/*
author: tobychui
*/
import (
"crypto/rand"
"crypto/sha512"
"errors"
"net/http"
"net/mail"
"strings"
"encoding/hex"
"log"
"github.com/gorilla/sessions"
db "imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/utils"
)
type AuthAgent struct {
//Session related
SessionName string
SessionStore *sessions.CookieStore
Database *db.Database
LoginRedirectionHandler func(http.ResponseWriter, *http.Request)
}
type AuthEndpoints struct {
Login string
Logout string
Register string
CheckLoggedIn string
Autologin string
}
//Constructor
func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent {
store := sessions.NewCookieStore(key)
err := sysdb.NewTable("auth")
if err != nil {
log.Println("Failed to create auth database. Terminating.")
panic(err)
}
//Create a new AuthAgent object
newAuthAgent := AuthAgent{
SessionName: sessionName,
SessionStore: store,
Database: sysdb,
LoginRedirectionHandler: loginRedirectionHandler,
}
//Return the authAgent
return &newAuthAgent
}
func GetSessionKey(sysdb *db.Database) (string, error) {
sysdb.NewTable("auth")
sessionKey := ""
if !sysdb.KeyExists("auth", "sessionkey") {
key := make([]byte, 32)
rand.Read(key)
sessionKey = string(key)
sysdb.Write("auth", "sessionkey", sessionKey)
log.Println("[Auth] New authentication session key generated")
} else {
log.Println("[Auth] Authentication session key loaded from database")
err := sysdb.Read("auth", "sessionkey", &sessionKey)
if err != nil {
return "", errors.New("database read error. Is the database file corrupted?")
}
}
return sessionKey, nil
}
//This function will handle an http request and redirect to the given login address if not logged in
func (a *AuthAgent) HandleCheckAuth(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request)) {
if a.CheckAuth(r) {
//User already logged in
handler(w, r)
} else {
//User not logged in
a.LoginRedirectionHandler(w, r)
}
}
//Handle login request, require POST username and password
func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
//Get username from request using POST mode
username, err := utils.PostPara(r, "username")
if err != nil {
//Username not defined
log.Println("[Auth] " + r.RemoteAddr + " trying to login with username: " + username)
utils.SendErrorResponse(w, "Username not defined or empty.")
return
}
//Get password from request using POST mode
password, err := utils.PostPara(r, "password")
if err != nil {
//Password not defined
utils.SendErrorResponse(w, "Password not defined or empty.")
return
}
//Get rememberme settings
rememberme := false
rmbme, _ := utils.PostPara(r, "rmbme")
if rmbme == "true" {
rememberme = true
}
//Check the database and see if this user is in the database
passwordCorrect, rejectionReason := a.ValidateUsernameAndPasswordWithReason(username, password)
//The database contain this user information. Check its password if it is correct
if passwordCorrect {
//Password correct
// Set user as authenticated
a.LoginUserByRequest(w, r, username, rememberme)
//Print the login message to console
log.Println(username + " logged in.")
utils.SendOK(w)
} else {
//Password incorrect
log.Println(username + " login request rejected: " + rejectionReason)
utils.SendErrorResponse(w, rejectionReason)
return
}
}
func (a *AuthAgent) ValidateUsernameAndPassword(username string, password string) bool {
succ, _ := a.ValidateUsernameAndPasswordWithReason(username, password)
return succ
}
//validate the username and password, return reasons if the auth failed
func (a *AuthAgent) ValidateUsernameAndPasswordWithReason(username string, password string) (bool, string) {
hashedPassword := Hash(password)
var passwordInDB string
err := a.Database.Read("auth", "passhash/"+username, &passwordInDB)
if err != nil {
//User not found or db exception
log.Println("[Auth] " + username + " login with incorrect password")
return false, "Invalid username or password"
}
if passwordInDB == hashedPassword {
return true, ""
} else {
return false, "Invalid username or password"
}
}
//Login the user by creating a valid session for this user
func (a *AuthAgent) LoginUserByRequest(w http.ResponseWriter, r *http.Request, username string, rememberme bool) {
session, _ := a.SessionStore.Get(r, a.SessionName)
session.Values["authenticated"] = true
session.Values["username"] = username
session.Values["rememberMe"] = rememberme
//Check if remember me is clicked. If yes, set the maxage to 1 week.
if rememberme {
session.Options = &sessions.Options{
MaxAge: 3600 * 24 * 7, //One week
Path: "/",
}
} else {
session.Options = &sessions.Options{
MaxAge: 3600 * 1, //One hour
Path: "/",
}
}
session.Save(r, w)
}
//Handle logout, reply OK after logged out. WILL NOT DO REDIRECTION
func (a *AuthAgent) HandleLogout(w http.ResponseWriter, r *http.Request) {
username, err := a.GetUserName(w, r)
if username != "" {
log.Println(username + " logged out.")
}
// Revoke users authentication
err = a.Logout(w, r)
if err != nil {
utils.SendErrorResponse(w, "Logout failed")
return
}
w.Write([]byte("OK"))
}
func (a *AuthAgent) Logout(w http.ResponseWriter, r *http.Request) error {
session, err := a.SessionStore.Get(r, a.SessionName)
if err != nil {
return err
}
session.Values["authenticated"] = false
session.Values["username"] = nil
session.Save(r, w)
return nil
}
//Get the current session username from request
func (a *AuthAgent) GetUserName(w http.ResponseWriter, r *http.Request) (string, error) {
if a.CheckAuth(r) {
//This user has logged in.
session, _ := a.SessionStore.Get(r, a.SessionName)
return session.Values["username"].(string), nil
} else {
//This user has not logged in.
return "", errors.New("user not logged in")
}
}
//Get the current session user email from request
func (a *AuthAgent) GetUserEmail(w http.ResponseWriter, r *http.Request) (string, error) {
if a.CheckAuth(r) {
//This user has logged in.
session, _ := a.SessionStore.Get(r, a.SessionName)
username := session.Values["username"].(string)
userEmail := ""
err := a.Database.Read("auth", "email/"+username, &userEmail)
if err != nil {
return "", err
}
return userEmail, nil
} else {
//This user has not logged in.
return "", errors.New("user not logged in")
}
}
//Check if the user has logged in, return true / false in JSON
func (a *AuthAgent) CheckLogin(w http.ResponseWriter, r *http.Request) {
if a.CheckAuth(r) {
utils.SendJSONResponse(w, "true")
} else {
utils.SendJSONResponse(w, "false")
}
}
//Handle new user register. Require POST username, password, group.
func (a *AuthAgent) HandleRegister(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
//Get username from request
newusername, err := utils.PostPara(r, "username")
if err != nil {
utils.SendErrorResponse(w, "Missing 'username' paramter")
return
}
//Get password from request
password, err := utils.PostPara(r, "password")
if err != nil {
utils.SendErrorResponse(w, "Missing 'password' paramter")
return
}
//Get email from request
email, err := utils.PostPara(r, "email")
if err != nil {
utils.SendErrorResponse(w, "Missing 'email' paramter")
return
}
_, err = mail.ParseAddress(email)
if err != nil {
utils.SendErrorResponse(w, "Invalid or malformed email")
return
}
//Ok to proceed create this user
err = a.CreateUserAccount(newusername, password, email)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Do callback if exists
if callback != nil {
callback(newusername, email)
}
//Return to the client with OK
utils.SendOK(w)
log.Println("[Auth] New user " + newusername + " added to system.")
}
//Handle new user register without confirmation email. Require POST username, password, group.
func (a *AuthAgent) HandleRegisterWithoutEmail(w http.ResponseWriter, r *http.Request, callback func(string, string)) {
//Get username from request
newusername, err := utils.PostPara(r, "username")
if err != nil {
utils.SendErrorResponse(w, "Missing 'username' paramter")
return
}
//Get password from request
password, err := utils.PostPara(r, "password")
if err != nil {
utils.SendErrorResponse(w, "Missing 'password' paramter")
return
}
//Ok to proceed create this user
err = a.CreateUserAccount(newusername, password, "")
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Do callback if exists
if callback != nil {
callback(newusername, "")
}
//Return to the client with OK
utils.SendOK(w)
log.Println("[Auth] Admin account created: " + newusername)
}
//Check authentication from request header's session value
func (a *AuthAgent) CheckAuth(r *http.Request) bool {
session, err := a.SessionStore.Get(r, a.SessionName)
if err != nil {
return false
}
// Check if user is authenticated
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
return false
}
return true
}
//Handle de-register of users. Require POST username.
//THIS FUNCTION WILL NOT CHECK FOR PERMISSION. PLEASE USE WITH PERMISSION HANDLER
func (a *AuthAgent) HandleUnregister(w http.ResponseWriter, r *http.Request) {
//Check if the user is logged in
if !a.CheckAuth(r) {
//This user has not logged in
utils.SendErrorResponse(w, "Login required to remove user from the system.")
return
}
//Get username from request
username, err := utils.PostPara(r, "username")
if err != nil {
utils.SendErrorResponse(w, "Missing 'username' paramter")
return
}
err = a.UnregisterUser(username)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Return to the client with OK
utils.SendOK(w)
log.Println("[Auth] User " + username + " has been removed from the system.")
}
func (a *AuthAgent) UnregisterUser(username string) error {
//Check if the user exists in the system database.
if !a.Database.KeyExists("auth", "passhash/"+username) {
//This user do not exists.
return errors.New("this user does not exists")
}
//OK! Remove the user from the database
a.Database.Delete("auth", "passhash/"+username)
a.Database.Delete("auth", "email/"+username)
return nil
}
//Get the number of users in the system
func (a *AuthAgent) GetUserCounts() int {
entries, _ := a.Database.ListTable("auth")
usercount := 0
for _, keypairs := range entries {
if strings.Contains(string(keypairs[0]), "passhash/") {
//This is a user registry
usercount++
}
}
if usercount == 0 {
log.Println("There are no user in the database.")
}
return usercount
}
//List all username within the system
func (a *AuthAgent) ListUsers() []string {
entries, _ := a.Database.ListTable("auth")
results := []string{}
for _, keypairs := range entries {
if strings.Contains(string(keypairs[0]), "passhash/") {
username := strings.Split(string(keypairs[0]), "/")[1]
results = append(results, username)
}
}
return results
}
//Check if the given username exists
func (a *AuthAgent) UserExists(username string) bool {
userpasswordhash := ""
err := a.Database.Read("auth", "passhash/"+username, &userpasswordhash)
if err != nil || userpasswordhash == "" {
return false
}
return true
}
//Update the session expire time given the request header.
func (a *AuthAgent) UpdateSessionExpireTime(w http.ResponseWriter, r *http.Request) bool {
session, _ := a.SessionStore.Get(r, a.SessionName)
if session.Values["authenticated"].(bool) {
//User authenticated. Extend its expire time
rememberme := session.Values["rememberMe"].(bool)
//Extend the session expire time
if rememberme {
session.Options = &sessions.Options{
MaxAge: 3600 * 24 * 7, //One week
Path: "/",
}
} else {
session.Options = &sessions.Options{
MaxAge: 3600 * 1, //One hour
Path: "/",
}
}
session.Save(r, w)
return true
} else {
return false
}
}
//Create user account
func (a *AuthAgent) CreateUserAccount(newusername string, password string, email string) error {
//Check user already exists
if a.UserExists(newusername) {
return errors.New("user with same name already exists")
}
key := newusername
hashedPassword := Hash(password)
err := a.Database.Write("auth", "passhash/"+key, hashedPassword)
if err != nil {
return err
}
if email != "" {
err = a.Database.Write("auth", "email/"+key, email)
if err != nil {
return err
}
}
return nil
}
//Hash the given raw string into sha512 hash
func Hash(raw string) string {
h := sha512.New()
h.Write([]byte(raw))
return hex.EncodeToString(h.Sum(nil))
}

View File

@ -1,53 +0,0 @@
package auth
import (
"errors"
"log"
"net/http"
)
type RouterOption struct {
AuthAgent *AuthAgent
RequireAuth bool //This router require authentication
DeniedHandler func(http.ResponseWriter, *http.Request) //Things to do when request is rejected
}
type RouterDef struct {
option RouterOption
endpoints map[string]func(http.ResponseWriter, *http.Request)
}
func NewManagedHTTPRouter(option RouterOption) *RouterDef {
return &RouterDef{
option: option,
endpoints: map[string]func(http.ResponseWriter, *http.Request){},
}
}
func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error {
//Check if the endpoint already registered
if _, exist := router.endpoints[endpoint]; exist {
log.Println("WARNING! Duplicated registering of web endpoint: " + endpoint)
return errors.New("endpoint register duplicated")
}
authAgent := router.option.AuthAgent
//OK. Register handler
http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
//Check authentication of the user
if router.option.RequireAuth {
authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) {
handler(w, r)
})
} else {
handler(w, r)
}
})
router.endpoints[endpoint] = handler
return nil
}

View File

@ -1,120 +0,0 @@
package database
/*
ArOZ Online Database Access Module
author: tobychui
This is an improved Object oriented base solution to the original
aroz online database script.
*/
import (
"sync"
)
type Database struct {
Db interface{} //This will be nil on openwrt and *bolt.DB in the rest of the systems
Tables sync.Map
ReadOnly bool
}
func NewDatabase(dbfile string, readOnlyMode bool) (*Database, error) {
return newDatabase(dbfile, readOnlyMode)
}
/*
Create / Drop a table
Usage:
err := sysdb.NewTable("MyTable")
err := sysdb.DropTable("MyTable")
*/
func (d *Database) UpdateReadWriteMode(readOnly bool) {
d.ReadOnly = readOnly
}
//Dump the whole db into a log file
func (d *Database) Dump(filename string) ([]string, error) {
return d.dump(filename)
}
//Create a new table
func (d *Database) NewTable(tableName string) error {
return d.newTable(tableName)
}
//Check is table exists
func (d *Database) TableExists(tableName string) bool {
return d.tableExists(tableName)
}
//Drop the given table
func (d *Database) DropTable(tableName string) error {
return d.dropTable(tableName)
}
/*
Write to database with given tablename and key. Example Usage:
type demo struct{
content string
}
thisDemo := demo{
content: "Hello World",
}
err := sysdb.Write("MyTable", "username/message",thisDemo);
*/
func (d *Database) Write(tableName string, key string, value interface{}) error {
return d.write(tableName, key, value)
}
/*
Read from database and assign the content to a given datatype. Example Usage:
type demo struct{
content string
}
thisDemo := new(demo)
err := sysdb.Read("MyTable", "username/message",&thisDemo);
*/
func (d *Database) Read(tableName string, key string, assignee interface{}) error {
return d.read(tableName, key, assignee)
}
func (d *Database) KeyExists(tableName string, key string) bool {
return d.keyExists(tableName, key)
}
/*
Delete a value from the database table given tablename and key
err := sysdb.Delete("MyTable", "username/message");
*/
func (d *Database) Delete(tableName string, key string) error {
return d.delete(tableName, key)
}
/*
//List table example usage
//Assume the value is stored as a struct named "groupstruct"
entries, err := sysdb.ListTable("test")
if err != nil {
panic(err)
}
for _, keypairs := range entries{
log.Println(string(keypairs[0]))
group := new(groupstruct)
json.Unmarshal(keypairs[1], &group)
log.Println(group);
}
*/
func (d *Database) ListTable(tableName string) ([][][]byte, error) {
return d.listTable(tableName)
}
func (d *Database) Close() {
d.close()
}

View File

@ -1,186 +0,0 @@
//go:build !mipsle && !riscv64
// +build !mipsle,!riscv64
package database
import (
"encoding/json"
"errors"
"log"
"sync"
"github.com/boltdb/bolt"
)
func newDatabase(dbfile string, readOnlyMode bool) (*Database, error) {
db, err := bolt.Open(dbfile, 0600, nil)
if err != nil {
return nil, err
}
tableMap := sync.Map{}
//Build the table list from database
err = db.View(func(tx *bolt.Tx) error {
return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
tableMap.Store(string(name), "")
return nil
})
})
return &Database{
Db: db,
Tables: tableMap,
ReadOnly: readOnlyMode,
}, err
}
//Dump the whole db into a log file
func (d *Database) dump(filename string) ([]string, error) {
results := []string{}
d.Tables.Range(func(tableName, v interface{}) bool {
entries, err := d.ListTable(tableName.(string))
if err != nil {
log.Println("Reading table " + tableName.(string) + " failed: " + err.Error())
return false
}
for _, keypairs := range entries {
results = append(results, string(keypairs[0])+":"+string(keypairs[1])+"\n")
}
return true
})
return results, nil
}
//Create a new table
func (d *Database) newTable(tableName string) error {
if d.ReadOnly == true {
return errors.New("Operation rejected in ReadOnly mode")
}
err := d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(tableName))
if err != nil {
return err
}
return nil
})
d.Tables.Store(tableName, "")
return err
}
//Check is table exists
func (d *Database) tableExists(tableName string) bool {
if _, ok := d.Tables.Load(tableName); ok {
return true
}
return false
}
//Drop the given table
func (d *Database) dropTable(tableName string) error {
if d.ReadOnly == true {
return errors.New("Operation rejected in ReadOnly mode")
}
err := d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
err := tx.DeleteBucket([]byte(tableName))
if err != nil {
return err
}
return nil
})
return err
}
//Write to table
func (d *Database) write(tableName string, key string, value interface{}) error {
if d.ReadOnly {
return errors.New("Operation rejected in ReadOnly mode")
}
jsonString, err := json.Marshal(value)
if err != nil {
return err
}
err = d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(tableName))
if err != nil {
return err
}
b := tx.Bucket([]byte(tableName))
err = b.Put([]byte(key), jsonString)
return err
})
return err
}
func (d *Database) read(tableName string, key string, assignee interface{}) error {
err := d.Db.(*bolt.DB).View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(tableName))
v := b.Get([]byte(key))
json.Unmarshal(v, &assignee)
return nil
})
return err
}
func (d *Database) keyExists(tableName string, key string) bool {
resultIsNil := false
if !d.TableExists(tableName) {
//Table not exists. Do not proceed accessing key
log.Println("[DB] ERROR: Requesting key from table that didn't exist!!!")
return false
}
err := d.Db.(*bolt.DB).View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(tableName))
v := b.Get([]byte(key))
if v == nil {
resultIsNil = true
}
return nil
})
if err != nil {
return false
} else {
if resultIsNil {
return false
} else {
return true
}
}
}
func (d *Database) delete(tableName string, key string) error {
if d.ReadOnly {
return errors.New("Operation rejected in ReadOnly mode")
}
err := d.Db.(*bolt.DB).Update(func(tx *bolt.Tx) error {
tx.Bucket([]byte(tableName)).Delete([]byte(key))
return nil
})
return err
}
func (d *Database) listTable(tableName string) ([][][]byte, error) {
var results [][][]byte
err := d.Db.(*bolt.DB).View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(tableName))
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
results = append(results, [][]byte{k, v})
}
return nil
})
return results, err
}
func (d *Database) close() {
d.Db.(*bolt.DB).Close()
}

View File

@ -1,208 +0,0 @@
//go:build mipsle || riscv64
// +build mipsle riscv64
package database
import (
"encoding/json"
"errors"
"log"
"os"
"path/filepath"
"strings"
"sync"
)
func newDatabase(dbfile string, readOnlyMode bool) (*Database, error) {
dbRootPath := filepath.ToSlash(filepath.Clean(dbfile))
dbRootPath = "fsdb/" + dbRootPath
err := os.MkdirAll(dbRootPath, 0755)
if err != nil {
return nil, err
}
tableMap := sync.Map{}
//build the table list from file system
files, err := filepath.Glob(filepath.Join(dbRootPath, "/*"))
if err != nil {
return nil, err
}
for _, file := range files {
if isDirectory(file) {
tableMap.Store(filepath.Base(file), "")
}
}
log.Println("Filesystem Emulated Key-value Database Service Started: " + dbRootPath)
return &Database{
Db: dbRootPath,
Tables: tableMap,
ReadOnly: readOnlyMode,
}, nil
}
func (d *Database) dump(filename string) ([]string, error) {
//Get all file objects from root
rootfiles, err := filepath.Glob(filepath.Join(d.Db.(string), "/*"))
if err != nil {
return []string{}, err
}
//Filter out the folders
rootFolders := []string{}
for _, file := range rootfiles {
if !isDirectory(file) {
rootFolders = append(rootFolders, filepath.Base(file))
}
}
return rootFolders, nil
}
func (d *Database) newTable(tableName string) error {
if d.ReadOnly {
return errors.New("Operation rejected in ReadOnly mode")
}
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
if !fileExists(tablePath) {
return os.MkdirAll(tablePath, 0755)
}
return nil
}
func (d *Database) tableExists(tableName string) bool {
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
if _, err := os.Stat(tablePath); errors.Is(err, os.ErrNotExist) {
return false
}
if !isDirectory(tablePath) {
return false
}
return true
}
func (d *Database) dropTable(tableName string) error {
if d.ReadOnly {
return errors.New("Operation rejected in ReadOnly mode")
}
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
if d.tableExists(tableName) {
return os.RemoveAll(tablePath)
} else {
return errors.New("table not exists")
}
}
func (d *Database) write(tableName string, key string, value interface{}) error {
if d.ReadOnly {
return errors.New("Operation rejected in ReadOnly mode")
}
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
js, err := json.Marshal(value)
if err != nil {
return err
}
key = strings.ReplaceAll(key, "/", "-SLASH_SIGN-")
return os.WriteFile(filepath.Join(tablePath, key+".entry"), js, 0755)
}
func (d *Database) read(tableName string, key string, assignee interface{}) error {
if !d.keyExists(tableName, key) {
return errors.New("key not exists")
}
key = strings.ReplaceAll(key, "/", "-SLASH_SIGN-")
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
entryPath := filepath.Join(tablePath, key+".entry")
content, err := os.ReadFile(entryPath)
if err != nil {
return err
}
err = json.Unmarshal(content, &assignee)
return err
}
func (d *Database) keyExists(tableName string, key string) bool {
key = strings.ReplaceAll(key, "/", "-SLASH_SIGN-")
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
entryPath := filepath.Join(tablePath, key+".entry")
return fileExists(entryPath)
}
func (d *Database) delete(tableName string, key string) error {
if d.ReadOnly {
return errors.New("Operation rejected in ReadOnly mode")
}
if !d.keyExists(tableName, key) {
return errors.New("key not exists")
}
key = strings.ReplaceAll(key, "/", "-SLASH_SIGN-")
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
entryPath := filepath.Join(tablePath, key+".entry")
return os.Remove(entryPath)
}
func (d *Database) listTable(tableName string) ([][][]byte, error) {
if !d.tableExists(tableName) {
return [][][]byte{}, errors.New("table not exists")
}
tablePath := filepath.Join(d.Db.(string), filepath.Base(tableName))
entries, err := filepath.Glob(filepath.Join(tablePath, "/*.entry"))
if err != nil {
return [][][]byte{}, err
}
var results [][][]byte = [][][]byte{}
for _, entry := range entries {
if !isDirectory(entry) {
//Read it
key := filepath.Base(entry)
key = strings.TrimSuffix(key, filepath.Ext(key))
key = strings.ReplaceAll(key, "-SLASH_SIGN-", "/")
bkey := []byte(key)
bval := []byte("")
c, err := os.ReadFile(entry)
if err != nil {
break
}
bval = c
results = append(results, [][]byte{bkey, bval})
}
}
return results, nil
}
func (d *Database) close() {
//Nothing to close as it is file system
}
func isDirectory(path string) bool {
fileInfo, err := os.Stat(path)
if err != nil {
return false
}
return fileInfo.IsDir()
}
func fileExists(name string) bool {
_, err := os.Stat(name)
if err == nil {
return true
}
if errors.Is(err, os.ErrNotExist) {
return false
}
return false
}

View File

@ -1,82 +0,0 @@
package dynamicproxy
import (
"net/http"
"os"
"strings"
"imuslab.com/zoraxy/mod/geodb"
)
/*
Server.go
Main server for dynamic proxy core
*/
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//Check if this ip is in blacklist
clientIpAddr := geodb.GetRequesterIP(r)
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusForbidden)
template, err := os.ReadFile("./web/forbidden.html")
if err != nil {
w.Write([]byte("403 - Forbidden"))
} else {
w.Write(template)
}
h.logRequest(r, false, 403, "blacklist")
return
}
//Check if this is a redirection url
if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
h.logRequest(r, statusCode != 500, statusCode, "redirect")
return
}
//Check if there are external routing rule matches.
//If yes, route them via external rr
matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
if matchedRoutingRule != nil {
//Matching routing rule found. Let the sub-router handle it
matchedRoutingRule.Route(w, r)
return
}
//Extract request host to see if it is virtual directory or subdomain
domainOnly := r.Host
if strings.Contains(r.Host, ":") {
hostPath := strings.Split(r.Host, ":")
domainOnly = hostPath[0]
}
if strings.Contains(r.Host, ".") {
//This might be a subdomain. See if there are any subdomain proxy router for this
//Remove the port if any
sep := h.Parent.getSubdomainProxyEndpointFromHostname(domainOnly)
if sep != nil {
h.subdomainRequest(w, r, sep)
return
}
}
//Clean up the request URI
proxyingPath := strings.TrimSpace(r.RequestURI)
targetProxyEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath)
if targetProxyEndpoint != nil {
h.proxyRequest(w, r, targetProxyEndpoint)
} else if !strings.HasSuffix(proxyingPath, "/") {
potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
if potentialProxtEndpoint != nil {
h.proxyRequest(w, r, potentialProxtEndpoint)
} else {
h.proxyRequest(w, r, h.Parent.Root)
}
} else {
h.proxyRequest(w, r, h.Parent.Root)
}
}

View File

@ -1,23 +0,0 @@
package domainsniff
import (
"net"
"time"
)
//Check if the domain is reachable and return err if not reachable
func DomainReachableWithError(domain string) error {
timeout := 1 * time.Second
conn, err := net.DialTimeout("tcp", domain, timeout)
if err != nil {
return err
}
conn.Close()
return nil
}
//Check if domain reachable
func DomainReachable(domain string) bool {
return DomainReachableWithError(domain) == nil
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018-present tobychui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,414 +0,0 @@
package dpcore
import (
"errors"
"io"
"log"
"net"
"net/http"
"net/url"
"path/filepath"
"strings"
"sync"
"time"
)
var onExitFlushLoop func()
const (
defaultTimeout = time.Minute * 5
)
// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client, support http, also support https tunnel using http.hijacker
type ReverseProxy struct {
// Set the timeout of the proxy server, default is 5 minutes
Timeout time.Duration
// Director must be a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Director must not access the provided Request
// after returning.
Director func(*http.Request)
// The transport used to perform proxy requests.
// default is http.DefaultTransport.
Transport http.RoundTripper
// FlushInterval specifies the flush interval
// to flush to the client while copying the
// response body. If zero, no periodic flushing is done.
FlushInterval time.Duration
// ErrorLog specifies an optional logger for errors
// that occur when attempting to proxy the request.
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog *log.Logger
// ModifyResponse is an optional function that
// modifies the Response from the backend.
// If it returns an error, the proxy returns a StatusBadGateway error.
ModifyResponse func(*http.Response) error
//Prepender is an optional prepend text for URL rewrite
//
Prepender string
Verbal bool
}
type requestCanceler interface {
CancelRequest(req *http.Request)
}
func NewDynamicProxyCore(target *url.URL, prepender string) *ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
// If Host is empty, the Request.Write method uses
// the value of URL.Host.
// force use URL.Host
req.Host = req.URL.Host
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
if _, ok := req.Header["User-Agent"]; !ok {
req.Header.Set("User-Agent", "")
}
}
return &ReverseProxy{
Director: director,
Prepender: prepender,
Verbal: false,
}
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
// Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
var hopHeaders = []string{
//"Connection",
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
"Transfer-Encoding",
//"Upgrade",
}
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
if p.FlushInterval != 0 {
if wf, ok := dst.(writeFlusher); ok {
mlw := &maxLatencyWriter{
dst: wf,
latency: p.FlushInterval,
done: make(chan bool),
}
go mlw.flushLoop()
defer mlw.stop()
dst = mlw
}
}
io.Copy(dst, src)
}
type writeFlusher interface {
io.Writer
http.Flusher
}
type maxLatencyWriter struct {
dst writeFlusher
latency time.Duration
mu sync.Mutex
done chan bool
}
func (m *maxLatencyWriter) Write(b []byte) (int, error) {
m.mu.Lock()
defer m.mu.Unlock()
return m.dst.Write(b)
}
func (m *maxLatencyWriter) flushLoop() {
t := time.NewTicker(m.latency)
defer t.Stop()
for {
select {
case <-m.done:
if onExitFlushLoop != nil {
onExitFlushLoop()
}
return
case <-t.C:
m.mu.Lock()
m.dst.Flush()
m.mu.Unlock()
}
}
}
func (m *maxLatencyWriter) stop() {
m.done <- true
}
func (p *ReverseProxy) logf(format string, args ...interface{}) {
if p.ErrorLog != nil {
p.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
func removeHeaders(header http.Header) {
// Remove hop-by-hop headers listed in the "Connection" header.
if c := header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
if f = strings.TrimSpace(f); f != "" {
header.Del(f)
}
}
}
// Remove hop-by-hop headers
for _, h := range hopHeaders {
if header.Get(h) != "" {
header.Del(h)
}
}
if header.Get("A-Upgrade") != "" {
header.Set("Upgrade", header.Get("A-Upgrade"))
header.Del("A-Upgrade")
}
}
func addXForwardedForHeader(req *http.Request) {
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
}
}
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request) error {
transport := p.Transport
if transport == nil {
transport = http.DefaultTransport
}
outreq := new(http.Request)
// Shallow copies of maps, like header
*outreq = *req
if cn, ok := rw.(http.CloseNotifier); ok {
if requestCanceler, ok := transport.(requestCanceler); ok {
// After the Handler has returned, there is no guarantee
// that the channel receives a value, so to make sure
reqDone := make(chan struct{})
defer close(reqDone)
clientGone := cn.CloseNotify()
go func() {
select {
case <-clientGone:
requestCanceler.CancelRequest(outreq)
case <-reqDone:
}
}()
}
}
p.Director(outreq)
outreq.Close = false
// We may modify the header (shallow copied above), so we only copy it.
outreq.Header = make(http.Header)
copyHeader(outreq.Header, req.Header)
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
removeHeaders(outreq.Header)
// Add X-Forwarded-For Header.
addXForwardedForHeader(outreq)
res, err := transport.RoundTrip(outreq)
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
//rw.WriteHeader(http.StatusBadGateway)
return err
}
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
removeHeaders(res.Header)
if p.ModifyResponse != nil {
if err := p.ModifyResponse(res); err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
//rw.WriteHeader(http.StatusBadGateway)
return err
}
}
//Custom header rewriter functions
if res.Header.Get("Location") != "" {
//Custom redirection to this rproxy relative path
res.Header.Set("Location", filepath.ToSlash(filepath.Join(p.Prepender, res.Header.Get("Location"))))
}
// Copy header from response to client.
copyHeader(rw.Header(), res.Header)
// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
if len(res.Trailer) > 0 {
trailerKeys := make([]string, 0, len(res.Trailer))
for k := range res.Trailer {
trailerKeys = append(trailerKeys, k)
}
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
}
rw.WriteHeader(res.StatusCode)
if len(res.Trailer) > 0 {
// Force chunking if we saw a response trailer.
// This prevents net/http from calculating the length for short
// bodies and adding a Content-Length.
if fl, ok := rw.(http.Flusher); ok {
fl.Flush()
}
}
p.copyResponse(rw, res.Body)
// close now, instead of defer, to populate res.Trailer
res.Body.Close()
copyHeader(rw.Header(), res.Trailer)
return nil
}
func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) error {
hij, ok := rw.(http.Hijacker)
if !ok {
p.logf("http server does not support hijacker")
return errors.New("http server does not support hijacker")
}
clientConn, _, err := hij.Hijack()
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
}
proxyConn, err := net.Dial("tcp", req.URL.Host)
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
}
// The returned net.Conn may have read or write deadlines
// already set, depending on the configuration of the
// Server, to set or clear those deadlines as needed
// we set timeout to 5 minutes
deadline := time.Now()
if p.Timeout == 0 {
deadline = deadline.Add(time.Minute * 5)
} else {
deadline = deadline.Add(p.Timeout)
}
err = clientConn.SetDeadline(deadline)
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
}
err = proxyConn.SetDeadline(deadline)
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
}
_, err = clientConn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
}
go func() {
io.Copy(clientConn, proxyConn)
clientConn.Close()
proxyConn.Close()
}()
io.Copy(proxyConn, clientConn)
proxyConn.Close()
clientConn.Close()
return nil
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) error {
if req.Method == "CONNECT" {
err := p.ProxyHTTPS(rw, req)
return err
} else {
err := p.ProxyHTTP(rw, req)
return err
}
}

View File

@ -1,347 +0,0 @@
package dynamicproxy
import (
"context"
"crypto/tls"
"errors"
"log"
"net"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/reverseproxy"
"imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/tlscert"
)
/*
Zoraxy Dynamic Proxy
*/
type RouterOption struct {
Port int
UseTls bool
ForceHttpsRedirect bool
TlsManager *tlscert.Manager
RedirectRuleTable *redirection.RuleTable
GeodbStore *geodb.Store
StatisticCollector *statistic.Collector
}
type Router struct {
Option *RouterOption
ProxyEndpoints *sync.Map
SubdomainEndpoint *sync.Map
Running bool
Root *ProxyEndpoint
mux http.Handler
server *http.Server
tlsListener net.Listener
routingRules []*RoutingRule
}
type ProxyEndpoint struct {
Root string
Domain string
RequireTLS bool
Proxy *dpcore.ReverseProxy `json:"-"`
}
type SubdomainEndpoint struct {
MatchingDomain string
Domain string
RequireTLS bool
Proxy *reverseproxy.ReverseProxy `json:"-"`
}
type ProxyHandler struct {
Parent *Router
}
func NewDynamicProxy(option RouterOption) (*Router, error) {
proxyMap := sync.Map{}
domainMap := sync.Map{}
thisRouter := Router{
Option: &option,
ProxyEndpoints: &proxyMap,
SubdomainEndpoint: &domainMap,
Running: false,
server: nil,
routingRules: []*RoutingRule{},
}
thisRouter.mux = &ProxyHandler{
Parent: &thisRouter,
}
return &thisRouter, nil
}
// Update TLS setting in runtime. Will restart the proxy server
// if it is already running in the background
func (router *Router) UpdateTLSSetting(tlsEnabled bool) {
router.Option.UseTls = tlsEnabled
router.Restart()
}
// Update https redirect, which will require updates
func (router *Router) UpdateHttpToHttpsRedirectSetting(useRedirect bool) {
router.Option.ForceHttpsRedirect = useRedirect
router.Restart()
}
// Start the dynamic routing
func (router *Router) StartProxyService() error {
//Create a new server object
if router.server != nil {
return errors.New("Reverse proxy server already running")
}
if router.Root == nil {
return errors.New("Reverse proxy router root not set")
}
config := &tls.Config{
GetCertificate: router.Option.TlsManager.GetCert,
}
if router.Option.UseTls {
//Serve with TLS mode
ln, err := tls.Listen("tcp", ":"+strconv.Itoa(router.Option.Port), config)
if err != nil {
log.Println(err)
return err
}
router.tlsListener = ln
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
router.Running = true
if router.Option.Port == 443 && router.Option.ForceHttpsRedirect {
//Add a 80 to 443 redirector
httpServer := &http.Server{
Addr: ":80",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusTemporaryRedirect)
}),
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
IdleTimeout: 120 * time.Second,
}
log.Println("Starting HTTP-to-HTTPS redirector (port 80)")
go func() {
//Start another router to check if the router.server is killed. If yes, kill this server as well
go func() {
for router.server != nil {
time.Sleep(100 * time.Millisecond)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
httpServer.Shutdown(ctx)
log.Println(":80 to :433 redirection listener stopped")
}()
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Could not start server: %v\n", err)
}
}()
}
log.Println("Reverse proxy service started in the background (TLS mode)")
go func() {
if err := router.server.Serve(ln); err != nil && err != http.ErrServerClosed {
log.Fatalf("Could not start server: %v\n", err)
}
}()
} else {
//Serve with non TLS mode
router.tlsListener = nil
router.server = &http.Server{Addr: ":" + strconv.Itoa(router.Option.Port), Handler: router.mux}
router.Running = true
log.Println("Reverse proxy service started in the background (Plain HTTP mode)")
go func() {
router.server.ListenAndServe()
//log.Println("[DynamicProxy] " + err.Error())
}()
}
return nil
}
func (router *Router) StopProxyService() error {
if router.server == nil {
return errors.New("Reverse proxy server already stopped")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := router.server.Shutdown(ctx)
if err != nil {
return err
}
if router.tlsListener != nil {
router.tlsListener.Close()
}
//Discard the server object
router.tlsListener = nil
router.server = nil
router.Running = false
return nil
}
// Restart the current router if it is running.
// Startup the server if it is not running initially
func (router *Router) Restart() error {
//Stop the router if it is already running
if router.Running {
err := router.StopProxyService()
if err != nil {
return err
}
}
//Start the server
err := router.StartProxyService()
return err
}
/*
Check if a given request is accessed via a proxied subdomain
*/
func (router *Router) IsProxiedSubdomain(r *http.Request) bool {
hostname := r.Header.Get("X-Forwarded-Host")
if hostname == "" {
hostname = r.Host
}
hostname = strings.Split(hostname, ":")[0]
subdEndpoint := router.getSubdomainProxyEndpointFromHostname(hostname)
return subdEndpoint != nil
}
/*
Add an URL into a custom proxy services
*/
func (router *Router) AddVirtualDirectoryProxyService(rootname string, domain string, requireTLS bool) error {
if domain[len(domain)-1:] == "/" {
domain = domain[:len(domain)-1]
}
if rootname[len(rootname)-1:] == "/" {
rootname = rootname[:len(rootname)-1]
}
webProxyEndpoint := domain
if requireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return err
}
proxy := dpcore.NewDynamicProxyCore(path, rootname)
endpointObject := ProxyEndpoint{
Root: rootname,
Domain: domain,
RequireTLS: requireTLS,
Proxy: proxy,
}
router.ProxyEndpoints.Store(rootname, &endpointObject)
log.Println("Adding Proxy Rule: ", rootname+" to "+domain)
return nil
}
/*
Remove routing from RP
*/
func (router *Router) RemoveProxy(ptype string, key string) error {
//fmt.Println(ptype, key)
if ptype == "vdir" {
router.ProxyEndpoints.Delete(key)
return nil
} else if ptype == "subd" {
router.SubdomainEndpoint.Delete(key)
return nil
}
return errors.New("invalid ptype")
}
/*
Add an default router for the proxy server
*/
func (router *Router) SetRootProxy(proxyLocation string, requireTLS bool) error {
if proxyLocation[len(proxyLocation)-1:] == "/" {
proxyLocation = proxyLocation[:len(proxyLocation)-1]
}
webProxyEndpoint := proxyLocation
if requireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return err
}
proxy := dpcore.NewDynamicProxyCore(path, "")
rootEndpoint := ProxyEndpoint{
Root: "/",
Domain: proxyLocation,
RequireTLS: requireTLS,
Proxy: proxy,
}
router.Root = &rootEndpoint
return nil
}
//Helpers to export the syncmap for easier processing
func (r *Router) GetSDProxyEndpointsAsMap() map[string]*SubdomainEndpoint {
m := make(map[string]*SubdomainEndpoint)
r.SubdomainEndpoint.Range(func(key, value interface{}) bool {
k, ok := key.(string)
if !ok {
return true
}
v, ok := value.(*SubdomainEndpoint)
if !ok {
return true
}
m[k] = v
return true
})
return m
}
func (r *Router) GetVDProxyEndpointsAsMap() map[string]*ProxyEndpoint {
m := make(map[string]*ProxyEndpoint)
r.ProxyEndpoints.Range(func(key, value interface{}) bool {
k, ok := key.(string)
if !ok {
return true
}
v, ok := value.(*ProxyEndpoint)
if !ok {
return true
}
m[k] = v
return true
})
return m
}

View File

@ -1,149 +0,0 @@
package dynamicproxy
import (
"errors"
"log"
"net"
"net/http"
"net/url"
"strings"
"imuslab.com/zoraxy/mod/geodb"
"imuslab.com/zoraxy/mod/statistic"
"imuslab.com/zoraxy/mod/websocketproxy"
)
func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
var targetProxyEndpoint *ProxyEndpoint = nil
router.ProxyEndpoints.Range(func(key, value interface{}) bool {
rootname := key.(string)
if strings.HasPrefix(requestURI, rootname) {
thisProxyEndpoint := value.(*ProxyEndpoint)
targetProxyEndpoint = thisProxyEndpoint
}
/*
if len(requestURI) >= len(rootname) && requestURI[:len(rootname)] == rootname {
thisProxyEndpoint := value.(*ProxyEndpoint)
targetProxyEndpoint = thisProxyEndpoint
}
*/
return true
})
return targetProxyEndpoint
}
func (router *Router) getSubdomainProxyEndpointFromHostname(hostname string) *SubdomainEndpoint {
var targetSubdomainEndpoint *SubdomainEndpoint = nil
ep, ok := router.SubdomainEndpoint.Load(hostname)
if ok {
targetSubdomainEndpoint = ep.(*SubdomainEndpoint)
}
return targetSubdomainEndpoint
}
func (router *Router) rewriteURL(rooturl string, requestURL string) string {
if len(requestURL) > len(rooturl) {
return requestURL[len(rooturl):]
}
return ""
}
func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request, target *SubdomainEndpoint) {
r.Header.Set("X-Forwarded-Host", r.Host)
requestURL := r.URL.String()
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
r.Header.Set("A-Upgrade", "websocket")
wsRedirectionEndpoint := target.Domain
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
//Append / to the end of the redirection endpoint if not exists
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
}
if len(requestURL) > 0 && requestURL[:1] == "/" {
//Remove starting / from request URL if exists
requestURL = requestURL[1:]
}
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + requestURL)
if target.RequireTLS {
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
}
h.logRequest(r, true, 101, "subdomain-websocket")
wspHandler := websocketproxy.NewProxy(u)
wspHandler.ServeHTTP(w, r)
return
}
r.Host = r.URL.Host
err := target.Proxy.ServeHTTP(w, r)
var dnsError *net.DNSError
if err != nil {
if errors.As(err, &dnsError) {
http.ServeFile(w, r, "./web/hosterror.html")
log.Println(err.Error())
h.logRequest(r, false, 404, "subdomain-http")
} else {
http.ServeFile(w, r, "./web/rperror.html")
log.Println(err.Error())
h.logRequest(r, false, 521, "subdomain-http")
}
}
h.logRequest(r, true, 200, "subdomain-http")
}
func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
rewriteURL := h.Parent.rewriteURL(target.Root, r.RequestURI)
r.URL, _ = url.Parse(rewriteURL)
r.Header.Set("X-Forwarded-Host", r.Host)
if r.Header["Upgrade"] != nil && strings.ToLower(r.Header["Upgrade"][0]) == "websocket" {
//Handle WebSocket request. Forward the custom Upgrade header and rewrite origin
r.Header.Set("A-Upgrade", "websocket")
wsRedirectionEndpoint := target.Domain
if wsRedirectionEndpoint[len(wsRedirectionEndpoint)-1:] != "/" {
wsRedirectionEndpoint = wsRedirectionEndpoint + "/"
}
u, _ := url.Parse("ws://" + wsRedirectionEndpoint + r.URL.String())
if target.RequireTLS {
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
}
h.logRequest(r, true, 101, "vdir-websocket")
wspHandler := websocketproxy.NewProxy(u)
wspHandler.ServeHTTP(w, r)
return
}
r.Host = r.URL.Host
err := target.Proxy.ServeHTTP(w, r)
var dnsError *net.DNSError
if err != nil {
if errors.As(err, &dnsError) {
http.ServeFile(w, r, "./web/hosterror.html")
log.Println(err.Error())
h.logRequest(r, false, 404, "vdir-http")
} else {
http.ServeFile(w, r, "./web/rperror.html")
log.Println(err.Error())
h.logRequest(r, false, 521, "vdir-http")
}
}
h.logRequest(r, true, 200, "vdir-http")
}
func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, forwardType string) {
if h.Parent.Option.StatisticCollector != nil {
go func() {
requestInfo := statistic.RequestInfo{
IpAddr: geodb.GetRequesterIP(r),
RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
Succ: succ,
StatusCode: statusCode,
ForwardType: forwardType,
}
h.Parent.Option.StatisticCollector.RecordRequest(requestInfo)
}()
}
}

View File

@ -1,53 +0,0 @@
package redirection
import (
"log"
"net/http"
"strings"
)
/*
handler.go
This script store the handlers use for handling
redirection request
*/
//Check if a request URL is a redirectable URI
func (t *RuleTable) IsRedirectable(r *http.Request) bool {
requestPath := r.Host + r.URL.Path
rr := t.MatchRedirectRule(requestPath)
return rr != nil
}
//Handle the redirect request, return after calling this function to prevent
//multiple write to the response writer
//Return the status code of the redirection handling
func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) int {
requestPath := r.Host + r.URL.Path
rr := t.MatchRedirectRule(requestPath)
if rr != nil {
redirectTarget := rr.TargetURL
//Always pad a / at the back of the target URL
if redirectTarget[len(redirectTarget)-1:] != "/" {
redirectTarget += "/"
}
if rr.ForwardChildpath {
//Remove the first / in the path
redirectTarget += r.URL.Path[1:] + "?" + r.URL.RawQuery
}
if !strings.HasPrefix(redirectTarget, "http://") && !strings.HasPrefix(redirectTarget, "https://") {
redirectTarget = "http://" + redirectTarget
}
http.Redirect(w, r, redirectTarget, rr.StatusCode)
return rr.StatusCode
} else {
//Invalid usage
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Internal Server Error"))
log.Println("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!")
return 500
}
}

View File

@ -1,162 +0,0 @@
package redirection
import (
"encoding/json"
"log"
"os"
"path"
"path/filepath"
"strings"
"sync"
"imuslab.com/zoraxy/mod/utils"
)
type RuleTable struct {
configPath string //The location where the redirection rules is stored
rules sync.Map //Store the redirection rules for this reverse proxy instance
}
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
}
func NewRuleTable(configPath string) (*RuleTable, error) {
thisRuleTable := RuleTable{
rules: sync.Map{},
configPath: configPath,
}
//Load all the rules from the config path
if !utils.FileExists(configPath) {
os.MkdirAll(configPath, 0775)
}
// Load all the *.json from the configPath
files, err := filepath.Glob(filepath.Join(configPath, "*.json"))
if err != nil {
return nil, err
}
// Parse the json content into RedirectRules
var rules []*RedirectRules
for _, file := range files {
b, err := os.ReadFile(file)
if err != nil {
continue
}
thisRule := RedirectRules{}
err = json.Unmarshal(b, &thisRule)
if err != nil {
continue
}
rules = append(rules, &thisRule)
}
//Map the rules into the sync map
for _, rule := range rules {
log.Println("Redirection rule added: " + rule.RedirectURL + " -> " + rule.TargetURL)
thisRuleTable.rules.Store(rule.RedirectURL, rule)
}
return &thisRuleTable, nil
}
func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardPathname bool, statusCode int) error {
// Create a new RedirectRules object with the given parameters
newRule := &RedirectRules{
RedirectURL: redirectURL,
TargetURL: destURL,
ForwardChildpath: forwardPathname,
StatusCode: statusCode,
}
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
// Create the full file path by joining the t.configPath with the filename
filepath := path.Join(t.configPath, filename)
// Create a new file for writing the JSON data
file, err := os.Create(filepath)
if err != nil {
log.Printf("Error creating file %s: %s", filepath, err)
return err
}
defer file.Close()
// Encode the RedirectRules object to JSON and write it to the file
err = json.NewEncoder(file).Encode(newRule)
if err != nil {
log.Printf("Error encoding JSON to file %s: %s", filepath, err)
return err
}
// Store the RedirectRules object in the sync.Map
t.rules.Store(redirectURL, newRule)
return nil
}
func (t *RuleTable) DeleteRedirectRule(redirectURL string) error {
// Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_"
filename := strings.ReplaceAll(strings.ReplaceAll(redirectURL, "/", "-"), ".", "_") + ".json"
// Create the full file path by joining the t.configPath with the filename
filepath := path.Join(t.configPath, filename)
// Check if the file exists
if _, err := os.Stat(filepath); os.IsNotExist(err) {
return nil // File doesn't exist, nothing to delete
}
// Delete the file
if err := os.Remove(filepath); err != nil {
log.Printf("Error deleting file %s: %s", filepath, err)
return err
}
// Delete the key-value pair from the sync.Map
t.rules.Delete(redirectURL)
return nil
}
// Get a list of all the redirection rules
func (t *RuleTable) GetAllRedirectRules() []*RedirectRules {
rules := []*RedirectRules{}
t.rules.Range(func(key, value interface{}) bool {
r, ok := value.(*RedirectRules)
if ok {
rules = append(rules, r)
}
return true
})
return rules
}
// Check if a given request URL matched any of the redirection rule
func (t *RuleTable) MatchRedirectRule(requestedURL string) *RedirectRules {
// Iterate through all the keys in the rules map
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
if strings.HasPrefix(requestedURL, key.(string)) {
// This request URL matched the domain
if len(key.(string)) > maxMatch {
maxMatch = len(key.(string))
targetRedirectionRule = value.(*RedirectRules)
}
}
return true
})
return targetRedirectionRule
}

View File

@ -1,85 +0,0 @@
package dynamicproxy
import (
"errors"
"net/http"
)
/*
Special.go
This script handle special routing rules
by external modules
*/
type RoutingRule struct {
ID string
MatchRule func(r *http.Request) bool
RoutingHandler http.Handler
Enabled bool
}
//Router functions
//Check if a routing rule exists given its id
func (router *Router) GetRoutingRuleById(rrid string) (*RoutingRule, error) {
for _, rr := range router.routingRules {
if rr.ID == rrid {
return rr, nil
}
}
return nil, errors.New("routing rule with given id not found")
}
//Add a routing rule to the router
func (router *Router) AddRoutingRules(rr *RoutingRule) error {
_, err := router.GetRoutingRuleById(rr.ID)
if err != nil {
//routing rule with given id already exists
return err
}
router.routingRules = append(router.routingRules, rr)
return nil
}
//Remove a routing rule from the router
func (router *Router) RemoveRoutingRule(rrid string) {
newRoutingRules := []*RoutingRule{}
for _, rr := range router.routingRules {
if rr.ID != rrid {
newRoutingRules = append(newRoutingRules, rr)
}
}
router.routingRules = newRoutingRules
}
//Get all routing rules
func (router *Router) GetAllRoutingRules() []*RoutingRule {
return router.routingRules
}
//Get the matching routing rule that describe this request.
//Return nil if no routing rule is match
func (router *Router) GetMatchingRoutingRule(r *http.Request) *RoutingRule {
for _, thisRr := range router.routingRules {
if thisRr.IsMatch(r) {
return thisRr
}
}
return nil
}
//Routing Rule functions
//Check if a request object match the
func (e *RoutingRule) IsMatch(r *http.Request) bool {
if !e.Enabled {
return false
}
return e.MatchRule(r)
}
func (e *RoutingRule) Route(w http.ResponseWriter, r *http.Request) {
e.RoutingHandler.ServeHTTP(w, r)
}

View File

@ -1,44 +0,0 @@
package dynamicproxy
import (
"log"
"net/url"
"imuslab.com/zoraxy/mod/reverseproxy"
)
/*
Add an URL intoa custom subdomain service
*/
func (router *Router) AddSubdomainRoutingService(hostnameWithSubdomain string, domain string, requireTLS bool) error {
if domain[len(domain)-1:] == "/" {
domain = domain[:len(domain)-1]
}
webProxyEndpoint := domain
if requireTLS {
webProxyEndpoint = "https://" + webProxyEndpoint
} else {
webProxyEndpoint = "http://" + webProxyEndpoint
}
//Create a new proxy agent for this root
path, err := url.Parse(webProxyEndpoint)
if err != nil {
return err
}
proxy := reverseproxy.NewReverseProxy(path)
router.SubdomainEndpoint.Store(hostnameWithSubdomain, &SubdomainEndpoint{
MatchingDomain: hostnameWithSubdomain,
Domain: domain,
RequireTLS: requireTLS,
Proxy: proxy,
})
log.Println("Adding Subdomain Rule: ", hostnameWithSubdomain+" to "+domain)
return nil
}

View File

@ -1,244 +0,0 @@
package geodb
import (
"net"
"net/http"
"strings"
"github.com/oschwald/geoip2-golang"
"imuslab.com/zoraxy/mod/database"
)
type Store struct {
Enabled bool
geodb *geoip2.Reader
sysdb *database.Database
}
type CountryInfo struct {
CountryIsoCode string
ContinetCode string
}
func NewGeoDb(sysdb *database.Database, dbfile string) (*Store, error) {
db, err := geoip2.Open(dbfile)
if err != nil {
return nil, err
}
err = sysdb.NewTable("blacklist-cn")
if err != nil {
return nil, err
}
err = sysdb.NewTable("blacklist-ip")
if err != nil {
return nil, err
}
err = sysdb.NewTable("blacklist")
if err != nil {
return nil, err
}
blacklistEnabled := false
sysdb.Read("blacklist", "enabled", &blacklistEnabled)
return &Store{
Enabled: blacklistEnabled,
geodb: db,
sysdb: sysdb,
}, nil
}
func (s *Store) ToggleBlacklist(enabled bool) {
s.sysdb.Write("blacklist", "enabled", enabled)
s.Enabled = enabled
}
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
// If you are using strings that may be invalid, check that ip is not nil
ip := net.ParseIP(ipstring)
record, err := s.geodb.City(ip)
if err != nil {
return nil, err
}
return &CountryInfo{
record.Country.IsoCode,
record.Continent.Code,
}, nil
}
func (s *Store) Close() {
s.geodb.Close()
}
func (s *Store) AddCountryCodeToBlackList(countryCode string) {
countryCode = strings.ToLower(countryCode)
s.sysdb.Write("blacklist-cn", countryCode, true)
}
func (s *Store) RemoveCountryCodeFromBlackList(countryCode string) {
countryCode = strings.ToLower(countryCode)
s.sysdb.Delete("blacklist-cn", countryCode)
}
func (s *Store) IsCountryCodeBlacklisted(countryCode string) bool {
countryCode = strings.ToLower(countryCode)
var isBlacklisted bool = false
s.sysdb.Read("blacklist-cn", countryCode, &isBlacklisted)
return isBlacklisted
}
func (s *Store) GetAllBlacklistedCountryCode() []string {
bannedCountryCodes := []string{}
entries, err := s.sysdb.ListTable("blacklist-cn")
if err != nil {
return bannedCountryCodes
}
for _, keypairs := range entries {
ip := string(keypairs[0])
bannedCountryCodes = append(bannedCountryCodes, ip)
}
return bannedCountryCodes
}
func (s *Store) AddIPToBlackList(ipAddr string) {
s.sysdb.Write("blacklist-ip", ipAddr, true)
}
func (s *Store) RemoveIPFromBlackList(ipAddr string) {
s.sysdb.Delete("blacklist-ip", ipAddr)
}
func (s *Store) IsIPBlacklisted(ipAddr string) bool {
var isBlacklisted bool = false
s.sysdb.Read("blacklist-ip", ipAddr, &isBlacklisted)
if isBlacklisted {
return true
}
//Check for IP wildcard and CIRD rules
AllBlacklistedIps := s.GetAllBlacklistedIp()
for _, blacklistRule := range AllBlacklistedIps {
wildcardMatch := MatchIpWildcard(ipAddr, blacklistRule)
if wildcardMatch {
return true
}
cidrMatch := MatchIpCIDR(ipAddr, blacklistRule)
if cidrMatch {
return true
}
}
return false
}
func (s *Store) GetAllBlacklistedIp() []string {
bannedIps := []string{}
entries, err := s.sysdb.ListTable("blacklist-ip")
if err != nil {
return bannedIps
}
for _, keypairs := range entries {
ip := string(keypairs[0])
bannedIps = append(bannedIps, ip)
}
return bannedIps
}
//Check if a IP address is blacklisted, in either country or IP blacklist
func (s *Store) IsBlacklisted(ipAddr string) bool {
if !s.Enabled {
//Blacklist not enabled. Always return false
return false
}
if ipAddr == "" {
//Unable to get the target IP address
return false
}
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
if err != nil {
return false
}
if s.IsCountryCodeBlacklisted(countryCode.CountryIsoCode) {
return true
}
if s.IsIPBlacklisted(ipAddr) {
return true
}
return false
}
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
ipAddr := GetRequesterIP(r)
if ipAddr == "" {
return ""
}
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
if err != nil {
return ""
}
return countryCode.CountryIsoCode
}
//Utilities function
func GetRequesterIP(r *http.Request) string {
ip := r.Header.Get("X-Forwarded-For")
if ip == "" {
ip = r.Header.Get("X-Real-IP")
if ip == "" {
ip = strings.Split(r.RemoteAddr, ":")[0]
}
}
return ip
}
//Match the IP address with a wildcard string
func MatchIpWildcard(ipAddress, wildcard string) bool {
// Split IP address and wildcard into octets
ipOctets := strings.Split(ipAddress, ".")
wildcardOctets := strings.Split(wildcard, ".")
// Check that both have 4 octets
if len(ipOctets) != 4 || len(wildcardOctets) != 4 {
return false
}
// Check each octet to see if it matches the wildcard or is an exact match
for i := 0; i < 4; i++ {
if wildcardOctets[i] == "*" {
continue
}
if ipOctets[i] != wildcardOctets[i] {
return false
}
}
return true
}
//Match ip address with CIDR
func MatchIpCIDR(ip string, cidr string) bool {
// parse the CIDR string
_, cidrnet, err := net.ParseCIDR(cidr)
if err != nil {
return false
}
// parse the IP address
ipAddr := net.ParseIP(ip)
// check if the IP address is within the CIDR range
return cidrnet.Contains(ipAddr)
}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018-present tobychui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,68 +0,0 @@
# Introduction
A minimalist proxy library for go, inspired by `net/http/httputil` and add support for HTTPS using HTTP Tunnel
Support cancels an in-flight request by closing it's connection
# Installation
```sh
go get github.com/cssivision/reverseproxy
```
# Usage
## A simple proxy
```go
package main
import (
"net/http"
"net/url"
"github.com/cssivision/reverseproxy"
)
func main() {
http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path, err := url.Parse("https://github.com")
if err != nil {
panic(err)
return
}
proxy := reverseproxy.NewReverseProxy(path)
proxy.ServeHTTP(w, r)
}))
}
```
## Use as a proxy server
To use proxy server, you should set browser to use the proxy server as an HTTP proxy.
```go
package main
import (
"net/http"
"net/url"
"github.com/cssivision/reverseproxy"
)
func main() {
http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path, err := url.Parse("http://" + r.Host)
if err != nil {
panic(err)
return
}
proxy := reverseproxy.NewReverseProxy(path)
proxy.ServeHTTP(w, r)
// Specific for HTTP and HTTPS
// if r.Method == "CONNECT" {
// proxy.ProxyHTTPS(w, r)
// } else {
// proxy.ProxyHTTP(w, r)
// }
}))
}
```

View File

@ -1,405 +0,0 @@
package reverseproxy
import (
"errors"
"io"
"log"
"net"
"net/http"
"net/url"
"strings"
"sync"
"time"
)
var onExitFlushLoop func()
const (
defaultTimeout = time.Minute * 5
)
// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client, support http, also support https tunnel using http.hijacker
type ReverseProxy struct {
// Set the timeout of the proxy server, default is 5 minutes
Timeout time.Duration
// Director must be a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Director must not access the provided Request
// after returning.
Director func(*http.Request)
// The transport used to perform proxy requests.
// default is http.DefaultTransport.
Transport http.RoundTripper
// FlushInterval specifies the flush interval
// to flush to the client while copying the
// response body. If zero, no periodic flushing is done.
FlushInterval time.Duration
// ErrorLog specifies an optional logger for errors
// that occur when attempting to proxy the request.
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog *log.Logger
// ModifyResponse is an optional function that
// modifies the Response from the backend.
// If it returns an error, the proxy returns a StatusBadGateway error.
ModifyResponse func(*http.Response) error
Verbal bool
}
type requestCanceler interface {
CancelRequest(req *http.Request)
}
// NewReverseProxy returns a new ReverseProxy that routes
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir. if the target's query is a=10
// and the incoming request's query is b=100, the target's request's query
// will be a=10&b=100.
// NewReverseProxy does not rewrite the Host header.
// To rewrite Host headers, use ReverseProxy directly with a custom
// Director policy.
func NewReverseProxy(target *url.URL) *ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
// If Host is empty, the Request.Write method uses
// the value of URL.Host.
// force use URL.Host
req.Host = req.URL.Host
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
if _, ok := req.Header["User-Agent"]; !ok {
req.Header.Set("User-Agent", "")
}
}
return &ReverseProxy{Director: director, Verbal: false}
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
// Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
var hopHeaders = []string{
//"Connection",
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailer", // not Trailers per URL above; http://www.rfc-editor.org/errata_search.php?eid=4522
"Transfer-Encoding",
//"Upgrade",
}
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
if p.FlushInterval != 0 {
if wf, ok := dst.(writeFlusher); ok {
mlw := &maxLatencyWriter{
dst: wf,
latency: p.FlushInterval,
done: make(chan bool),
}
go mlw.flushLoop()
defer mlw.stop()
dst = mlw
}
}
io.Copy(dst, src)
}
type writeFlusher interface {
io.Writer
http.Flusher
}
type maxLatencyWriter struct {
dst writeFlusher
latency time.Duration
mu sync.Mutex
done chan bool
}
func (m *maxLatencyWriter) Write(b []byte) (int, error) {
m.mu.Lock()
defer m.mu.Unlock()
return m.dst.Write(b)
}
func (m *maxLatencyWriter) flushLoop() {
t := time.NewTicker(m.latency)
defer t.Stop()
for {
select {
case <-m.done:
if onExitFlushLoop != nil {
onExitFlushLoop()
}
return
case <-t.C:
m.mu.Lock()
m.dst.Flush()
m.mu.Unlock()
}
}
}
func (m *maxLatencyWriter) stop() {
m.done <- true
}
func (p *ReverseProxy) logf(format string, args ...interface{}) {
if p.ErrorLog != nil {
p.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
func removeHeaders(header http.Header) {
// Remove hop-by-hop headers listed in the "Connection" header.
if c := header.Get("Connection"); c != "" {
for _, f := range strings.Split(c, ",") {
if f = strings.TrimSpace(f); f != "" {
header.Del(f)
}
}
}
// Remove hop-by-hop headers
for _, h := range hopHeaders {
if header.Get(h) != "" {
header.Del(h)
}
}
if header.Get("A-Upgrade") != "" {
header.Set("Upgrade", header.Get("A-Upgrade"))
header.Del("A-Upgrade")
}
}
func addXForwardedForHeader(req *http.Request) {
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
}
}
func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request) error {
transport := p.Transport
if transport == nil {
transport = http.DefaultTransport
}
outreq := new(http.Request)
// Shallow copies of maps, like header
*outreq = *req
if cn, ok := rw.(http.CloseNotifier); ok {
if requestCanceler, ok := transport.(requestCanceler); ok {
// After the Handler has returned, there is no guarantee
// that the channel receives a value, so to make sure
reqDone := make(chan struct{})
defer close(reqDone)
clientGone := cn.CloseNotify()
go func() {
select {
case <-clientGone:
requestCanceler.CancelRequest(outreq)
case <-reqDone:
}
}()
}
}
p.Director(outreq)
outreq.Close = false
// We may modify the header (shallow copied above), so we only copy it.
outreq.Header = make(http.Header)
copyHeader(outreq.Header, req.Header)
// Remove hop-by-hop headers listed in the "Connection" header, Remove hop-by-hop headers.
removeHeaders(outreq.Header)
// Add X-Forwarded-For Header.
addXForwardedForHeader(outreq)
res, err := transport.RoundTrip(outreq)
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
//rw.WriteHeader(http.StatusBadGateway)
return err
}
// Remove hop-by-hop headers listed in the "Connection" header of the response, Remove hop-by-hop headers.
removeHeaders(res.Header)
if p.ModifyResponse != nil {
if err := p.ModifyResponse(res); err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
//rw.WriteHeader(http.StatusBadGateway)
return err
}
}
// Copy header from response to client.
copyHeader(rw.Header(), res.Header)
// The "Trailer" header isn't included in the Transport's response, Build it up from Trailer.
if len(res.Trailer) > 0 {
trailerKeys := make([]string, 0, len(res.Trailer))
for k := range res.Trailer {
trailerKeys = append(trailerKeys, k)
}
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
}
rw.WriteHeader(res.StatusCode)
if len(res.Trailer) > 0 {
// Force chunking if we saw a response trailer.
// This prevents net/http from calculating the length for short
// bodies and adding a Content-Length.
if fl, ok := rw.(http.Flusher); ok {
fl.Flush()
}
}
p.copyResponse(rw, res.Body)
// close now, instead of defer, to populate res.Trailer
res.Body.Close()
copyHeader(rw.Header(), res.Trailer)
return nil
}
func (p *ReverseProxy) ProxyHTTPS(rw http.ResponseWriter, req *http.Request) error {
hij, ok := rw.(http.Hijacker)
if !ok {
p.logf("http server does not support hijacker")
return errors.New("http server does not support hijacker")
}
clientConn, _, err := hij.Hijack()
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
}
proxyConn, err := net.Dial("tcp", req.URL.Host)
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
}
// The returned net.Conn may have read or write deadlines
// already set, depending on the configuration of the
// Server, to set or clear those deadlines as needed
// we set timeout to 5 minutes
deadline := time.Now()
if p.Timeout == 0 {
deadline = deadline.Add(time.Minute * 5)
} else {
deadline = deadline.Add(p.Timeout)
}
err = clientConn.SetDeadline(deadline)
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
}
err = proxyConn.SetDeadline(deadline)
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
}
_, err = clientConn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
if err != nil {
if p.Verbal {
p.logf("http: proxy error: %v", err)
}
return err
}
go func() {
io.Copy(clientConn, proxyConn)
clientConn.Close()
proxyConn.Close()
}()
io.Copy(proxyConn, clientConn)
proxyConn.Close()
clientConn.Close()
return nil
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) error {
if req.Method == "CONNECT" {
err := p.ProxyHTTPS(rw, req)
return err
} else {
err := p.ProxyHTTP(rw, req)
return err
}
}

View File

@ -1,67 +0,0 @@
package statistic
import (
"encoding/json"
"net/http"
"imuslab.com/zoraxy/mod/utils"
)
/*
Handler.go
This script handles incoming request for loading the statistic of the day
*/
func (c *Collector) HandleTodayStatLoad(w http.ResponseWriter, r *http.Request) {
fast, err := utils.GetPara(r, "fast")
if err != nil {
fast = "false"
}
d := c.DailySummary
if fast == "true" {
//Only return the counter
exported := DailySummaryExport{
TotalRequest: d.TotalRequest,
ErrorRequest: d.ErrorRequest,
ValidRequest: d.ValidRequest,
}
js, _ := json.Marshal(exported)
utils.SendJSONResponse(w, string(js))
} else {
//Return everything
exported := DailySummaryExport{
TotalRequest: d.TotalRequest,
ErrorRequest: d.ErrorRequest,
ValidRequest: d.ValidRequest,
ForwardTypes: make(map[string]int),
RequestOrigin: make(map[string]int),
RequestClientIp: make(map[string]int),
}
// Export ForwardTypes sync.Map
d.ForwardTypes.Range(func(key, value interface{}) bool {
exported.ForwardTypes[key.(string)] = value.(int)
return true
})
// Export RequestOrigin sync.Map
d.RequestOrigin.Range(func(key, value interface{}) bool {
exported.RequestOrigin[key.(string)] = value.(int)
return true
})
// Export RequestClientIp sync.Map
d.RequestClientIp.Range(func(key, value interface{}) bool {
exported.RequestClientIp[key.(string)] = value.(int)
return true
})
js, _ := json.Marshal(exported)
utils.SendJSONResponse(w, string(js))
}
}

View File

@ -1,186 +0,0 @@
package statistic
import (
"strings"
"sync"
"time"
"imuslab.com/zoraxy/mod/database"
)
/*
Statistic Package
This packet is designed to collection information
and store them for future analysis
*/
//Faststat, a interval summary for all collected data and avoid
//looping through every data everytime a overview is needed
type DailySummary struct {
TotalRequest int64 //Total request of the day
ErrorRequest int64 //Invalid request of the day, including error or not found
ValidRequest int64 //Valid request of the day
//Type counters
ForwardTypes *sync.Map //Map that hold the forward types
RequestOrigin *sync.Map //Map that hold [country ISO code]: visitor counter
RequestClientIp *sync.Map //Map that hold all unique request IPs
}
type RequestInfo struct {
IpAddr string
RequestOriginalCountryISOCode string
Succ bool
StatusCode int
ForwardType string
}
type CollectorOption struct {
Database *database.Database
}
type Collector struct {
rtdataStopChan chan bool
DailySummary *DailySummary
Option *CollectorOption
}
func NewStatisticCollector(option CollectorOption) (*Collector, error) {
option.Database.NewTable("stats")
//Create the collector object
thisCollector := Collector{
DailySummary: newDailySummary(),
Option: &option,
}
//Load the stat if exists for today
//This will exists if the program was forcefully restarted
year, month, day := time.Now().Date()
summary := thisCollector.LoadSummaryOfDay(year, month, day)
if summary != nil {
thisCollector.DailySummary = summary
}
//Schedule the realtime statistic clearing at midnight everyday
rtstatStopChan := thisCollector.ScheduleResetRealtimeStats()
thisCollector.rtdataStopChan = rtstatStopChan
return &thisCollector, nil
}
//Write the current in-memory summary to database file
func (c *Collector) SaveSummaryOfDay() {
//When it is called in 0:00am, make sure it is stored as yesterday key
t := time.Now().Add(-30 * time.Second)
summaryKey := t.Format("02_01_2006")
saveData := DailySummaryToExport(*c.DailySummary)
c.Option.Database.Write("stats", summaryKey, saveData)
}
//Load the summary of a day given
func (c *Collector) LoadSummaryOfDay(year int, month time.Month, day int) *DailySummary {
date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
summaryKey := date.Format("02_01_2006")
targetSummaryExport := DailySummaryExport{}
c.Option.Database.Read("stats", summaryKey, &targetSummaryExport)
targetSummary := DailySummaryExportToSummary(targetSummaryExport)
return &targetSummary
}
//This function gives the current slot in the 288- 5 minutes interval of the day
func (c *Collector) GetCurrentRealtimeStatIntervalId() int {
now := time.Now()
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).Unix()
secondsSinceStartOfDay := now.Unix() - startOfDay
interval := secondsSinceStartOfDay / (5 * 60)
return int(interval)
}
func (c *Collector) Close() {
//Stop the ticker
c.rtdataStopChan <- true
//Write the buffered data into database
c.SaveSummaryOfDay()
}
//Main function to record all the inbound traffics
//Note that this function run in go routine and might have concurrent R/W issue
//Please make sure there is no racing paramters in this function
func (c *Collector) RecordRequest(ri RequestInfo) {
go func() {
c.DailySummary.TotalRequest++
if ri.Succ {
c.DailySummary.ValidRequest++
} else {
c.DailySummary.ErrorRequest++
}
//Store the request info into correct types of maps
ft, ok := c.DailySummary.ForwardTypes.Load(ri.ForwardType)
if !ok {
c.DailySummary.ForwardTypes.Store(ri.ForwardType, 1)
} else {
c.DailySummary.ForwardTypes.Store(ri.ForwardType, ft.(int)+1)
}
originISO := strings.ToLower(ri.RequestOriginalCountryISOCode)
fo, ok := c.DailySummary.RequestOrigin.Load(originISO)
if !ok {
c.DailySummary.RequestOrigin.Store(originISO, 1)
} else {
c.DailySummary.RequestOrigin.Store(originISO, fo.(int)+1)
}
fi, ok := c.DailySummary.RequestClientIp.Load(ri.IpAddr)
if !ok {
c.DailySummary.RequestClientIp.Store(ri.IpAddr, 1)
} else {
c.DailySummary.RequestClientIp.Store(ri.IpAddr, fi.(int)+1)
}
}()
}
//nightly task
func (c *Collector) ScheduleResetRealtimeStats() chan bool {
doneCh := make(chan bool)
go func() {
defer close(doneCh)
for {
// calculate duration until next midnight
now := time.Now()
// Get midnight of the next day in the local time zone
midnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
// Calculate the duration until midnight
duration := midnight.Sub(now)
select {
case <-time.After(duration):
// store daily summary to database and reset summary
c.SaveSummaryOfDay()
c.DailySummary = newDailySummary()
case <-doneCh:
// stop the routine
return
}
}
}()
return doneCh
}
func newDailySummary() *DailySummary {
return &DailySummary{
TotalRequest: 0,
ErrorRequest: 0,
ValidRequest: 0,
ForwardTypes: &sync.Map{},
RequestOrigin: &sync.Map{},
RequestClientIp: &sync.Map{},
}
}

View File

@ -1,66 +0,0 @@
package statistic
import "sync"
type DailySummaryExport struct {
TotalRequest int64 //Total request of the day
ErrorRequest int64 //Invalid request of the day, including error or not found
ValidRequest int64 //Valid request of the day
ForwardTypes map[string]int
RequestOrigin map[string]int
RequestClientIp map[string]int
}
func DailySummaryToExport(summary DailySummary) DailySummaryExport {
export := DailySummaryExport{
TotalRequest: summary.TotalRequest,
ErrorRequest: summary.ErrorRequest,
ValidRequest: summary.ValidRequest,
ForwardTypes: make(map[string]int),
RequestOrigin: make(map[string]int),
RequestClientIp: make(map[string]int),
}
summary.ForwardTypes.Range(func(key, value interface{}) bool {
export.ForwardTypes[key.(string)] = value.(int)
return true
})
summary.RequestOrigin.Range(func(key, value interface{}) bool {
export.RequestOrigin[key.(string)] = value.(int)
return true
})
summary.RequestClientIp.Range(func(key, value interface{}) bool {
export.RequestClientIp[key.(string)] = value.(int)
return true
})
return export
}
func DailySummaryExportToSummary(export DailySummaryExport) DailySummary {
summary := DailySummary{
TotalRequest: export.TotalRequest,
ErrorRequest: export.ErrorRequest,
ValidRequest: export.ValidRequest,
ForwardTypes: &sync.Map{},
RequestOrigin: &sync.Map{},
RequestClientIp: &sync.Map{},
}
for k, v := range export.ForwardTypes {
summary.ForwardTypes.Store(k, v)
}
for k, v := range export.RequestOrigin {
summary.RequestOrigin.Store(k, v)
}
for k, v := range export.RequestClientIp {
summary.RequestClientIp.Store(k, v)
}
return summary
}

View File

@ -1,60 +0,0 @@
package tlscert
import (
"path/filepath"
"strings"
)
//This remove the certificates in the list where either the
//public key or the private key is missing
func getCertPairs(certFiles []string) []string {
crtMap := make(map[string]bool)
keyMap := make(map[string]bool)
for _, filename := range certFiles {
if filepath.Ext(filename) == ".crt" {
crtMap[strings.TrimSuffix(filename, ".crt")] = true
} else if filepath.Ext(filename) == ".key" {
keyMap[strings.TrimSuffix(filename, ".key")] = true
}
}
var result []string
for domain := range crtMap {
if keyMap[domain] {
result = append(result, domain)
}
}
return result
}
//Get the cloest subdomain certificate from a list of domains
func matchClosestDomainCertificate(subdomain string, domains []string) string {
var matchingDomain string = ""
maxLength := 0
for _, domain := range domains {
if strings.HasSuffix(subdomain, "."+domain) && len(domain) > maxLength {
matchingDomain = domain
maxLength = len(domain)
}
}
return matchingDomain
}
//Check if a requesting domain is a subdomain of a given domain
func isSubdomain(subdomain, domain string) bool {
subdomainParts := strings.Split(subdomain, ".")
domainParts := strings.Split(domain, ".")
if len(subdomainParts) < len(domainParts) {
return false
}
for i := range domainParts {
if subdomainParts[len(subdomainParts)-1-i] != domainParts[len(domainParts)-1-i] {
return false
}
}
return true
}

View File

@ -1,172 +0,0 @@
package tlscert
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"imuslab.com/zoraxy/mod/utils"
)
type Manager struct {
CertStore string
verbal bool
}
func NewManager(certStore string) (*Manager, error) {
if !utils.FileExists(certStore) {
os.MkdirAll(certStore, 0775)
}
thisManager := Manager{
CertStore: certStore,
verbal: true,
}
return &thisManager, nil
}
func (m *Manager) ListCertDomains() ([]string, error) {
filenames, err := m.ListCerts()
if err != nil {
return []string{}, err
}
//Remove certificates where there are missing public key or private key
filenames = getCertPairs(filenames)
return filenames, nil
}
func (m *Manager) ListCerts() ([]string, error) {
certs, err := ioutil.ReadDir(m.CertStore)
if err != nil {
return []string{}, err
}
filenames := make([]string, 0, len(certs))
for _, cert := range certs {
if !cert.IsDir() {
filenames = append(filenames, cert.Name())
}
}
return filenames, nil
}
func (m *Manager) GetCert(helloInfo *tls.ClientHelloInfo) (*tls.Certificate, error) {
//Check if the domain corrisponding cert exists
pubKey := "./system/localhost.crt"
priKey := "./system/localhost.key"
if utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".crt")) && utils.FileExists(filepath.Join(m.CertStore, helloInfo.ServerName+".key")) {
pubKey = filepath.Join(m.CertStore, helloInfo.ServerName+".crt")
priKey = filepath.Join(m.CertStore, helloInfo.ServerName+".key")
} else {
domainCerts, _ := m.ListCertDomains()
cloestDomainCert := matchClosestDomainCertificate(helloInfo.ServerName, domainCerts)
if cloestDomainCert != "" {
//There is a matching parent domain for this subdomain. Use this instead.
pubKey = filepath.Join(m.CertStore, cloestDomainCert+".crt")
priKey = filepath.Join(m.CertStore, cloestDomainCert+".key")
} else if m.DefaultCertExists() {
//Use default.crt and default.key
pubKey = filepath.Join(m.CertStore, "default.crt")
priKey = filepath.Join(m.CertStore, "default.key")
if m.verbal {
log.Println("No matching certificate found. Serving with default")
}
} else {
if m.verbal {
log.Println("Matching certificate not found. Serving with default. Requesting server name: ", helloInfo.ServerName)
}
}
}
//Load the cert and serve it
cer, err := tls.LoadX509KeyPair(pubKey, priKey)
if err != nil {
log.Println(err)
return nil, nil
}
return &cer, nil
}
//Check if both the default cert public key and private key exists
func (m *Manager) DefaultCertExists() bool {
return utils.FileExists(filepath.Join(m.CertStore, "default.crt")) && utils.FileExists(filepath.Join(m.CertStore, "default.key"))
}
//Check if the default cert exists returning seperate results for pubkey and prikey
func (m *Manager) DefaultCertExistsSep() (bool, bool) {
return utils.FileExists(filepath.Join(m.CertStore, "default.crt")), utils.FileExists(filepath.Join(m.CertStore, "default.key"))
}
//Delete the cert if exists
func (m *Manager) RemoveCert(domain string) error {
pubKey := filepath.Join(m.CertStore, domain+".crt")
priKey := filepath.Join(m.CertStore, domain+".key")
if utils.FileExists(pubKey) {
err := os.Remove(pubKey)
if err != nil {
return err
}
}
if utils.FileExists(priKey) {
err := os.Remove(priKey)
if err != nil {
return err
}
}
return nil
}
//Check if the given file is a valid TLS file
func IsValidTLSFile(file io.Reader) bool {
// Read the contents of the uploaded file
contents, err := io.ReadAll(file)
if err != nil {
// Handle the error
return false
}
// Parse the contents of the file as a PEM-encoded certificate or key
block, _ := pem.Decode(contents)
if block == nil {
// The file is not a valid PEM-encoded certificate or key
return false
}
// Parse the certificate or key
if strings.Contains(block.Type, "CERTIFICATE") {
// The file contains a certificate
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
// Handle the error
return false
}
// Check if the certificate is a valid TLS/SSL certificate
return cert.IsCA == false && cert.KeyUsage&x509.KeyUsageDigitalSignature != 0 && cert.KeyUsage&x509.KeyUsageKeyEncipherment != 0
} else if strings.Contains(block.Type, "PRIVATE KEY") {
// The file contains a private key
_, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
// Handle the error
return false
}
return true
} else {
return false
}
}

View File

@ -1,127 +0,0 @@
package upnp
import (
"errors"
"log"
"sync"
"time"
"gitlab.com/NebulousLabs/go-upnp"
)
/*
uPNP Module
This module handles uPNP Connections to the gateway router and create a port forward entry
for the host system at the given port (set with -port paramter)
*/
type UPnPClient struct {
Connection *upnp.IGD //UPnP conenction object
ExternalIP string //Storage of external IP address
RequiredPorts []int //All the required ports will be recored
PolicyNames sync.Map //Name for the required port nubmer
}
func NewUPNPClient() (*UPnPClient, error) {
//Create uPNP forwarding in the NAT router
log.Println("Discovering UPnP router in Local Area Network...")
d, err := upnp.Discover()
if err != nil {
return &UPnPClient{}, err
}
// discover external IP
ip, err := d.ExternalIP()
if err != nil {
return &UPnPClient{}, err
}
//Create the final obejcts
newUPnPObject := &UPnPClient{
Connection: d,
ExternalIP: ip,
RequiredPorts: []int{},
}
return newUPnPObject, nil
}
func (u *UPnPClient) ForwardPort(portNumber int, ruleName string) error {
log.Println("UPnP forwarding new port: ", portNumber, "for "+ruleName+" service")
//Check if port already forwarded
_, ok := u.PolicyNames.Load(portNumber)
if ok {
//Port already forward. Ignore this request
return errors.New("Port already forwarded")
}
// forward a port
err := u.Connection.Forward(uint16(portNumber), ruleName)
if err != nil {
return err
}
u.RequiredPorts = append(u.RequiredPorts, portNumber)
u.PolicyNames.Store(portNumber, ruleName)
return nil
}
func (u *UPnPClient) ClosePort(portNumber int) error {
//Check if port is opened
portOpened := false
newRequiredPort := []int{}
for _, thisPort := range u.RequiredPorts {
if thisPort != portNumber {
newRequiredPort = append(newRequiredPort, thisPort)
} else {
portOpened = true
}
}
if portOpened {
//Update the port list
u.RequiredPorts = newRequiredPort
// Close the port
log.Println("Closing UPnP Port Forward: ", portNumber)
err := u.Connection.Clear(uint16(portNumber))
//Delete the name registry
u.PolicyNames.Delete(portNumber)
if err != nil {
log.Println(err)
return err
}
}
return nil
}
// Renew forward rules, prevent router lease time from flushing the Upnp config
func (u *UPnPClient) RenewForwardRules() {
portsToRenew := u.RequiredPorts
for _, thisPort := range portsToRenew {
ruleName, ok := u.PolicyNames.Load(thisPort)
if !ok {
continue
}
u.ClosePort(thisPort)
time.Sleep(100 * time.Millisecond)
u.ForwardPort(thisPort, ruleName.(string))
}
log.Println("UPnP Port Forward rule renew completed")
}
func (u *UPnPClient) Close() {
//Shutdown the default UPnP Object
if u != nil {
for _, portNumber := range u.RequiredPorts {
err := u.Connection.Clear(uint16(portNumber))
if err != nil {
log.Println(err)
}
}
}
}

View File

@ -1,227 +0,0 @@
package uptime
import (
"encoding/json"
"log"
"net/http"
"time"
"imuslab.com/zoraxy/mod/utils"
)
type Record struct {
Timestamp int64
ID string
Name string
URL string
Protocol string
Online bool
StatusCode int
Latency int64
}
type Target struct {
ID string
Name string
URL string
Protocol string
}
type Config struct {
Targets []*Target
Interval int
MaxRecordsStore int
}
type Monitor struct {
Config *Config
OnlineStatusLog map[string][]*Record
}
// Default configs
var exampleTarget = Target{
ID: "example",
Name: "Example",
URL: "example.com",
Protocol: "https",
}
//Create a new uptime monitor
func NewUptimeMonitor(config *Config) (*Monitor, error) {
//Create new monitor object
thisMonitor := Monitor{
Config: config,
OnlineStatusLog: map[string][]*Record{},
}
//Start the endpoint listener
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
done := make(chan bool)
//Start the uptime check once first before entering loop
thisMonitor.ExecuteUptimeCheck()
go func() {
for {
select {
case <-done:
return
case t := <-ticker.C:
log.Println("Uptime updated - ", t.Unix())
thisMonitor.ExecuteUptimeCheck()
}
}
}()
return &thisMonitor, nil
}
func (m *Monitor) ExecuteUptimeCheck() {
for _, target := range m.Config.Targets {
//For each target to check online, do the following
var thisRecord Record
if target.Protocol == "http" || target.Protocol == "https" {
online, laterncy, statusCode := getWebsiteStatusWithLatency(target.URL)
thisRecord = Record{
Timestamp: time.Now().Unix(),
ID: target.ID,
Name: target.Name,
URL: target.URL,
Protocol: target.Protocol,
Online: online,
StatusCode: statusCode,
Latency: laterncy,
}
//fmt.Println(thisRecord)
} else {
log.Println("Unknown protocol: " + target.Protocol + ". Skipping")
continue
}
thisRecords, ok := m.OnlineStatusLog[target.ID]
if !ok {
//First record. Create the array
m.OnlineStatusLog[target.ID] = []*Record{&thisRecord}
} else {
//Append to the previous record
thisRecords = append(thisRecords, &thisRecord)
//Check if the record is longer than the logged record. If yes, clear out the old records
if len(thisRecords) > m.Config.MaxRecordsStore {
thisRecords = thisRecords[1:]
}
m.OnlineStatusLog[target.ID] = thisRecords
}
}
//TODO: Write results to db
}
func (m *Monitor) AddTargetToMonitor(target *Target) {
// Add target to Config
m.Config.Targets = append(m.Config.Targets, target)
// Add target to OnlineStatusLog
m.OnlineStatusLog[target.ID] = []*Record{}
}
func (m *Monitor) RemoveTargetFromMonitor(targetId string) {
// Remove target from Config
for i, target := range m.Config.Targets {
if target.ID == targetId {
m.Config.Targets = append(m.Config.Targets[:i], m.Config.Targets[i+1:]...)
break
}
}
// Remove target from OnlineStatusLog
delete(m.OnlineStatusLog, targetId)
}
//Scan the config target. If a target exists in m.OnlineStatusLog no longer
//exists in m.Monitor.Config.Targets, it remove it from the log as well.
func (m *Monitor) CleanRecords() {
// Create a set of IDs for all targets in the config
targetIDs := make(map[string]bool)
for _, target := range m.Config.Targets {
targetIDs[target.ID] = true
}
// Iterate over all log entries and remove any that have a target ID that
// is not in the set of current target IDs
newStatusLog := m.OnlineStatusLog
for id, _ := range m.OnlineStatusLog {
_, idExistsInTargets := targetIDs[id]
if !idExistsInTargets {
delete(newStatusLog, id)
}
}
m.OnlineStatusLog = newStatusLog
}
/*
Web Interface Handler
*/
func (m *Monitor) HandleUptimeLogRead(w http.ResponseWriter, r *http.Request) {
id, _ := utils.GetPara(r, "id")
if id == "" {
js, _ := json.Marshal(m.OnlineStatusLog)
w.Header().Set("Content-Type", "application/json")
w.Write(js)
} else {
//Check if that id exists
log, ok := m.OnlineStatusLog[id]
if !ok {
http.NotFound(w, r)
return
}
js, _ := json.MarshalIndent(log, "", " ")
w.Header().Set("Content-Type", "application/json")
w.Write(js)
}
}
/*
Utilities
*/
// Get website stauts with latency given URL, return is conn succ and its latency and status code
func getWebsiteStatusWithLatency(url string) (bool, int64, int) {
start := time.Now().UnixNano() / int64(time.Millisecond)
statusCode, err := getWebsiteStatus(url)
end := time.Now().UnixNano() / int64(time.Millisecond)
if err != nil {
log.Println(err.Error())
return false, 0, 0
} else {
diff := end - start
succ := false
if statusCode >= 200 && statusCode < 300 {
//OK
succ = true
} else if statusCode >= 300 && statusCode < 400 {
//Redirection code
succ = true
} else {
succ = false
}
return succ, diff, statusCode
}
}
func getWebsiteStatus(url string) (int, error) {
resp, err := http.Get(url)
if err != nil {
return 0, err
}
status_code := resp.StatusCode
return status_code, nil
}

View File

@ -1,16 +0,0 @@
package utils
import "strconv"
func StringToInt64(number string) (int64, error) {
i, err := strconv.ParseInt(number, 10, 64)
if err != nil {
return -1, err
}
return i, nil
}
func Int64ToString(number int64) string {
convedNumber := strconv.FormatInt(number, 10)
return convedNumber
}

View File

@ -1,19 +0,0 @@
package utils
import (
"net/http"
)
/*
Web Template Generator
This is the main system core module that perform function similar to what PHP did.
To replace part of the content of any file, use {{paramter}} to replace it.
*/
func SendHTMLResponse(w http.ResponseWriter, msg string) {
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(msg))
}

View File

@ -1,175 +0,0 @@
package utils
import (
"bufio"
"encoding/base64"
"errors"
"io"
"log"
"net/http"
"os"
"strings"
"time"
)
/*
Common
Some commonly used functions in ArozOS
*/
// Response related
func SendTextResponse(w http.ResponseWriter, msg string) {
w.Write([]byte(msg))
}
// Send JSON response, with an extra json header
func SendJSONResponse(w http.ResponseWriter, json string) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(json))
}
func SendErrorResponse(w http.ResponseWriter, errMsg string) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{\"error\":\"" + errMsg + "\"}"))
}
func SendOK(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("\"OK\""))
}
/*
The paramter move function (mv)
You can find similar things in the PHP version of ArOZ Online Beta. You need to pass in
r (HTTP Request Object)
getParamter (string, aka $_GET['This string])
Will return
Paramter string (if any)
Error (if error)
*/
/*
func Mv(r *http.Request, getParamter string, postMode bool) (string, error) {
if postMode == false {
//Access the paramter via GET
keys, ok := r.URL.Query()[getParamter]
if !ok || len(keys[0]) < 1 {
//log.Println("Url Param " + getParamter +" is missing")
return "", errors.New("GET paramter " + getParamter + " not found or it is empty")
}
// Query()["key"] will return an array of items,
// we only want the single item.
key := keys[0]
return string(key), nil
} else {
//Access the parameter via POST
r.ParseForm()
x := r.Form.Get(getParamter)
if len(x) == 0 || x == "" {
return "", errors.New("POST paramter " + getParamter + " not found or it is empty")
}
return string(x), nil
}
}
*/
// Get GET parameter
func GetPara(r *http.Request, key string) (string, error) {
keys, ok := r.URL.Query()[key]
if !ok || len(keys[0]) < 1 {
return "", errors.New("invalid " + key + " given")
} else {
return keys[0], nil
}
}
// Get POST paramter
func PostPara(r *http.Request, key string) (string, error) {
r.ParseForm()
x := r.Form.Get(key)
if x == "" {
return "", errors.New("invalid " + key + " given")
} else {
return x, nil
}
}
func FileExists(filename string) bool {
_, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return true
}
func IsDir(path string) bool {
if FileExists(path) == false {
return false
}
fi, err := os.Stat(path)
if err != nil {
log.Fatal(err)
return false
}
switch mode := fi.Mode(); {
case mode.IsDir():
return true
case mode.IsRegular():
return false
}
return false
}
func TimeToString(targetTime time.Time) string {
return targetTime.Format("2006-01-02 15:04:05")
}
func LoadImageAsBase64(filepath string) (string, error) {
if !FileExists(filepath) {
return "", errors.New("File not exists")
}
f, _ := os.Open(filepath)
reader := bufio.NewReader(f)
content, _ := io.ReadAll(reader)
encoded := base64.StdEncoding.EncodeToString(content)
return string(encoded), nil
}
// Use for redirections
func ConstructRelativePathFromRequestURL(requestURI string, redirectionLocation string) string {
if strings.Count(requestURI, "/") == 1 {
//Already root level
return redirectionLocation
}
for i := 0; i < strings.Count(requestURI, "/")-1; i++ {
redirectionLocation = "../" + redirectionLocation
}
return redirectionLocation
}
// Check if given string in a given slice
func StringInArray(arr []string, str string) bool {
for _, a := range arr {
if a == str {
return true
}
}
return false
}
func StringInArrayIgnoreCase(arr []string, str string) bool {
smallArray := []string{}
for _, item := range arr {
smallArray = append(smallArray, strings.ToLower(item))
}
return StringInArray(smallArray, strings.ToLower(str))
}

View File

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Koding, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,54 +0,0 @@
# WebsocketProxy [![GoDoc](https://godoc.org/github.com/koding/websocketproxy?status.svg)](https://godoc.org/github.com/koding/websocketproxy) [![Build Status](https://travis-ci.org/koding/websocketproxy.svg)](https://travis-ci.org/koding/websocketproxy)
WebsocketProxy is an http.Handler interface build on top of
[gorilla/websocket](https://github.com/gorilla/websocket) that you can plug
into your existing Go webserver to provide WebSocket reverse proxy.
## Install
```bash
go get github.com/koding/websocketproxy
```
## Example
Below is a simple server that proxies to the given backend URL
```go
package main
import (
"flag"
"net/http"
"net/url"
"github.com/koding/websocketproxy"
)
var (
flagBackend = flag.String("backend", "", "Backend URL for proxying")
)
func main() {
u, err := url.Parse(*flagBackend)
if err != nil {
log.Fatalln(err)
}
err = http.ListenAndServe(":80", websocketproxy.NewProxy(u))
if err != nil {
log.Fatalln(err)
}
}
```
Save it as `proxy.go` and run as:
```bash
go run proxy.go -backend ws://example.com:3000
```
Now all incoming WebSocket requests coming to this server will be proxied to
`ws://example.com:3000`

View File

@ -1,239 +0,0 @@
// Package websocketproxy is a reverse proxy for WebSocket connections.
package websocketproxy
import (
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"strings"
"github.com/gorilla/websocket"
)
var (
// DefaultUpgrader specifies the parameters for upgrading an HTTP
// connection to a WebSocket connection.
DefaultUpgrader = &websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
// DefaultDialer is a dialer with all fields set to the default zero values.
DefaultDialer = websocket.DefaultDialer
)
// WebsocketProxy is an HTTP Handler that takes an incoming WebSocket
// connection and proxies it to another server.
type WebsocketProxy struct {
// Director, if non-nil, is a function that may copy additional request
// headers from the incoming WebSocket connection into the output headers
// which will be forwarded to another server.
Director func(incoming *http.Request, out http.Header)
// Backend returns the backend URL which the proxy uses to reverse proxy
// the incoming WebSocket connection. Request is the initial incoming and
// unmodified request.
Backend func(*http.Request) *url.URL
// Upgrader specifies the parameters for upgrading a incoming HTTP
// connection to a WebSocket connection. If nil, DefaultUpgrader is used.
Upgrader *websocket.Upgrader
// Dialer contains options for connecting to the backend WebSocket server.
// If nil, DefaultDialer is used.
Dialer *websocket.Dialer
Verbal bool
}
// ProxyHandler returns a new http.Handler interface that reverse proxies the
// request to the given target.
func ProxyHandler(target *url.URL) http.Handler { return NewProxy(target) }
// NewProxy returns a new Websocket reverse proxy that rewrites the
// URL's to the scheme, host and base path provider in target.
func NewProxy(target *url.URL) *WebsocketProxy {
backend := func(r *http.Request) *url.URL {
// Shallow copy
u := *target
u.Fragment = r.URL.Fragment
u.Path = r.URL.Path
u.RawQuery = r.URL.RawQuery
return &u
}
return &WebsocketProxy{Backend: backend, Verbal: false}
}
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if w.Backend == nil {
log.Println("websocketproxy: backend function is not defined")
http.Error(rw, "internal server error (code: 1)", http.StatusInternalServerError)
return
}
backendURL := w.Backend(req)
if backendURL == nil {
log.Println("websocketproxy: backend URL is nil")
http.Error(rw, "internal server error (code: 2)", http.StatusInternalServerError)
return
}
dialer := w.Dialer
if w.Dialer == nil {
dialer = DefaultDialer
}
// Pass headers from the incoming request to the dialer to forward them to
// the final destinations.
requestHeader := http.Header{}
if origin := req.Header.Get("Origin"); origin != "" {
requestHeader.Add("Origin", origin)
}
for _, prot := range req.Header[http.CanonicalHeaderKey("Sec-WebSocket-Protocol")] {
requestHeader.Add("Sec-WebSocket-Protocol", prot)
}
for _, cookie := range req.Header[http.CanonicalHeaderKey("Cookie")] {
requestHeader.Add("Cookie", cookie)
}
if req.Host != "" {
requestHeader.Set("Host", req.Host)
}
// Pass X-Forwarded-For headers too, code below is a part of
// httputil.ReverseProxy. See http://en.wikipedia.org/wiki/X-Forwarded-For
// for more information
// TODO: use RFC7239 http://tools.ietf.org/html/rfc7239
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
requestHeader.Set("X-Forwarded-For", clientIP)
}
// Set the originating protocol of the incoming HTTP request. The SSL might
// be terminated on our site and because we doing proxy adding this would
// be helpful for applications on the backend.
requestHeader.Set("X-Forwarded-Proto", "http")
if req.TLS != nil {
requestHeader.Set("X-Forwarded-Proto", "https")
}
// Enable the director to copy any additional headers it desires for
// forwarding to the remote server.
if w.Director != nil {
w.Director(req, requestHeader)
}
// Connect to the backend URL, also pass the headers we get from the requst
// together with the Forwarded headers we prepared above.
// TODO: support multiplexing on the same backend connection instead of
// opening a new TCP connection time for each request. This should be
// optional:
// http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-01
connBackend, resp, err := dialer.Dial(backendURL.String(), requestHeader)
if err != nil {
log.Printf("websocketproxy: couldn't dial to remote backend url %s", err)
if resp != nil {
// If the WebSocket handshake fails, ErrBadHandshake is returned
// along with a non-nil *http.Response so that callers can handle
// redirects, authentication, etcetera.
if err := copyResponse(rw, resp); err != nil {
log.Printf("websocketproxy: couldn't write response after failed remote backend handshake: %s", err)
}
} else {
http.Error(rw, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
}
return
}
defer connBackend.Close()
upgrader := w.Upgrader
if w.Upgrader == nil {
upgrader = DefaultUpgrader
}
// Only pass those headers to the upgrader.
upgradeHeader := http.Header{}
if hdr := resp.Header.Get("Sec-Websocket-Protocol"); hdr != "" {
upgradeHeader.Set("Sec-Websocket-Protocol", hdr)
}
if hdr := resp.Header.Get("Set-Cookie"); hdr != "" {
upgradeHeader.Set("Set-Cookie", hdr)
}
// Now upgrade the existing incoming request to a WebSocket connection.
// Also pass the header that we gathered from the Dial handshake.
connPub, err := upgrader.Upgrade(rw, req, upgradeHeader)
if err != nil {
log.Printf("websocketproxy: couldn't upgrade %s", err)
return
}
defer connPub.Close()
errClient := make(chan error, 1)
errBackend := make(chan error, 1)
replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error) {
for {
msgType, msg, err := src.ReadMessage()
if err != nil {
m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
if e, ok := err.(*websocket.CloseError); ok {
if e.Code != websocket.CloseNoStatusReceived {
m = websocket.FormatCloseMessage(e.Code, e.Text)
}
}
errc <- err
dst.WriteMessage(websocket.CloseMessage, m)
break
}
err = dst.WriteMessage(msgType, msg)
if err != nil {
errc <- err
break
}
}
}
go replicateWebsocketConn(connPub, connBackend, errClient)
go replicateWebsocketConn(connBackend, connPub, errBackend)
var message string
select {
case err = <-errClient:
message = "websocketproxy: Error when copying from backend to client: %v"
case err = <-errBackend:
message = "websocketproxy: Error when copying from client to backend: %v"
}
if e, ok := err.(*websocket.CloseError); !ok || e.Code == websocket.CloseAbnormalClosure {
if w.Verbal {
//Only print message on verbal mode
log.Printf(message, err)
}
}
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
func copyResponse(rw http.ResponseWriter, resp *http.Response) error {
copyHeader(rw.Header(), resp.Header)
rw.WriteHeader(resp.StatusCode)
defer resp.Body.Close()
_, err := io.Copy(rw, resp.Body)
return err
}

View File

@ -1,130 +0,0 @@
package websocketproxy
import (
"log"
"net/http"
"net/url"
"testing"
"time"
"github.com/gorilla/websocket"
)
var (
serverURL = "ws://127.0.0.1:7777"
backendURL = "ws://127.0.0.1:8888"
)
func TestProxy(t *testing.T) {
// websocket proxy
supportedSubProtocols := []string{"test-protocol"}
upgrader := &websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool {
return true
},
Subprotocols: supportedSubProtocols,
}
u, _ := url.Parse(backendURL)
proxy := NewProxy(u)
proxy.Upgrader = upgrader
mux := http.NewServeMux()
mux.Handle("/proxy", proxy)
go func() {
if err := http.ListenAndServe(":7777", mux); err != nil {
t.Fatal("ListenAndServe: ", err)
}
}()
time.Sleep(time.Millisecond * 100)
// backend echo server
go func() {
mux2 := http.NewServeMux()
mux2.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Don't upgrade if original host header isn't preserved
if r.Host != "127.0.0.1:7777" {
log.Printf("Host header set incorrectly. Expecting 127.0.0.1:7777 got %s", r.Host)
return
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
messageType, p, err := conn.ReadMessage()
if err != nil {
return
}
if err = conn.WriteMessage(messageType, p); err != nil {
return
}
})
err := http.ListenAndServe(":8888", mux2)
if err != nil {
t.Fatal("ListenAndServe: ", err)
}
}()
time.Sleep(time.Millisecond * 100)
// let's us define two subprotocols, only one is supported by the server
clientSubProtocols := []string{"test-protocol", "test-notsupported"}
h := http.Header{}
for _, subprot := range clientSubProtocols {
h.Add("Sec-WebSocket-Protocol", subprot)
}
// frontend server, dial now our proxy, which will reverse proxy our
// message to the backend websocket server.
conn, resp, err := websocket.DefaultDialer.Dial(serverURL+"/proxy", h)
if err != nil {
t.Fatal(err)
}
// check if the server really accepted only the first one
in := func(desired string) bool {
for _, prot := range resp.Header[http.CanonicalHeaderKey("Sec-WebSocket-Protocol")] {
if desired == prot {
return true
}
}
return false
}
if !in("test-protocol") {
t.Error("test-protocol should be available")
}
if in("test-notsupported") {
t.Error("test-notsupported should be not recevied from the server.")
}
// now write a message and send it to the backend server (which goes trough
// proxy..)
msg := "hello kite"
err = conn.WriteMessage(websocket.TextMessage, []byte(msg))
if err != nil {
t.Error(err)
}
messageType, p, err := conn.ReadMessage()
if err != nil {
t.Error(err)
}
if messageType != websocket.TextMessage {
t.Error("incoming message type is not Text")
}
if msg != string(p) {
t.Errorf("expecting: %s, got: %s", msg, string(p))
}
}

View File

@ -1,75 +0,0 @@
package main
import (
"encoding/json"
"net/http"
"strconv"
"imuslab.com/zoraxy/mod/utils"
)
/*
Redirect.go
This script handle all the http handlers
related to redirection function in the reverse proxy
*/
func handleListRedirectionRules(w http.ResponseWriter, r *http.Request) {
rules := redirectTable.GetAllRedirectRules()
js, _ := json.Marshal(rules)
utils.SendJSONResponse(w, string(js))
}
func handleAddRedirectionRule(w http.ResponseWriter, r *http.Request) {
redirectUrl, err := utils.PostPara(r, "redirectUrl")
if err != nil {
utils.SendErrorResponse(w, "redirect url cannot be empty")
return
}
destUrl, err := utils.PostPara(r, "destUrl")
if err != nil {
utils.SendErrorResponse(w, "destination url cannot be empty")
}
forwardChildpath, err := utils.PostPara(r, "forwardChildpath")
if err != nil {
//Assume true
forwardChildpath = "true"
}
redirectTypeString, err := utils.PostPara(r, "redirectType")
if err != nil {
redirectTypeString = "307"
}
redirectionStatusCode, err := strconv.Atoi(redirectTypeString)
if err != nil {
utils.SendErrorResponse(w, "invalid status code number")
return
}
err = redirectTable.AddRedirectRule(redirectUrl, destUrl, forwardChildpath == "true", redirectionStatusCode)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
func handleDeleteRedirectionRule(w http.ResponseWriter, r *http.Request) {
redirectUrl, err := utils.PostPara(r, "redirectUrl")
if err != nil {
utils.SendErrorResponse(w, "redirect url cannot be empty")
return
}
err = redirectTable.DeleteRedirectRule(redirectUrl)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}

View File

@ -1,340 +0,0 @@
package main
import (
"encoding/json"
"log"
"net/http"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"imuslab.com/zoraxy/mod/dynamicproxy"
"imuslab.com/zoraxy/mod/uptime"
"imuslab.com/zoraxy/mod/utils"
)
var (
dynamicProxyRouter *dynamicproxy.Router
)
// Add user customizable reverse proxy
func ReverseProxtInit() {
inboundPort := 80
if sysdb.KeyExists("settings", "inbound") {
sysdb.Read("settings", "inbound", &inboundPort)
log.Println("Serving inbound port ", inboundPort)
} else {
log.Println("Inbound port not set. Using default (80)")
}
useTls := false
sysdb.Read("settings", "usetls", &useTls)
if useTls {
log.Println("TLS mode enabled. Serving proxxy request with TLS")
} else {
log.Println("TLS mode disabled. Serving proxy request with plain http")
}
forceHttpsRedirect := false
sysdb.Read("settings", "redirect", &forceHttpsRedirect)
if forceHttpsRedirect {
log.Println("Force HTTPS mode enabled")
} else {
log.Println("Force HTTPS mode disabled")
}
dprouter, err := dynamicproxy.NewDynamicProxy(dynamicproxy.RouterOption{
Port: inboundPort,
UseTls: useTls,
ForceHttpsRedirect: forceHttpsRedirect,
TlsManager: tlsCertManager,
RedirectRuleTable: redirectTable,
GeodbStore: geodbStore,
StatisticCollector: statisticCollector,
})
if err != nil {
log.Println(err.Error())
return
}
dynamicProxyRouter = dprouter
//Load all conf from files
confs, _ := filepath.Glob("./conf/*.config")
for _, conf := range confs {
record, err := LoadReverseProxyConfig(conf)
if err != nil {
log.Println("Failed to load "+filepath.Base(conf), err.Error())
return
}
if record.ProxyType == "root" {
dynamicProxyRouter.SetRootProxy(record.ProxyTarget, record.UseTLS)
} else if record.ProxyType == "subd" {
dynamicProxyRouter.AddSubdomainRoutingService(record.Rootname, record.ProxyTarget, record.UseTLS)
} else if record.ProxyType == "vdir" {
dynamicProxyRouter.AddVirtualDirectoryProxyService(record.Rootname, record.ProxyTarget, record.UseTLS)
} else {
log.Println("Unsupported endpoint type: " + record.ProxyType + ". Skipping " + filepath.Base(conf))
}
}
/*
dynamicProxyRouter.SetRootProxy("192.168.0.107:8080", false)
dynamicProxyRouter.AddSubdomainRoutingService("aroz.localhost", "192.168.0.107:8080/private/AOB/", false)
dynamicProxyRouter.AddSubdomainRoutingService("loopback.localhost", "localhost:8080", false)
dynamicProxyRouter.AddSubdomainRoutingService("git.localhost", "mc.alanyeung.co:3000", false)
dynamicProxyRouter.AddVirtualDirectoryProxyService("/git/server/", "mc.alanyeung.co:3000", false)
*/
//Start Service
//Not sure why but delay must be added if you have another
//reverse proxy server in front of this service
time.Sleep(300 * time.Millisecond)
dynamicProxyRouter.StartProxyService()
log.Println("Dynamic Reverse Proxy service started")
//Add all proxy services to uptime monitor
//Create a uptime monitor service
go func() {
//This must be done in go routine to prevent blocking on system startup
uptimeMonitor, _ = uptime.NewUptimeMonitor(&uptime.Config{
Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
Interval: 300, //5 minutes
MaxRecordsStore: 288, //1 day
})
log.Println("Uptime Monitor background service started")
}()
}
func ReverseProxyHandleOnOff(w http.ResponseWriter, r *http.Request) {
enable, _ := utils.PostPara(r, "enable") //Support root, vdir and subd
if enable == "true" {
err := dynamicProxyRouter.StartProxyService()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
} else {
//Check if it is loopback
if dynamicProxyRouter.IsProxiedSubdomain(r) {
//Loopback routing. Turning it off will make the user lost control
//of the whole system. Do not allow shutdown
utils.SendErrorResponse(w, "Unable to shutdown in loopback rp mode. Remove proxy rules for management interface and retry.")
return
}
err := dynamicProxyRouter.StopProxyService()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
}
utils.SendOK(w)
}
func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
eptype, err := utils.PostPara(r, "type") //Support root, vdir and subd
if err != nil {
utils.SendErrorResponse(w, "type not defined")
return
}
endpoint, err := utils.PostPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "endpoint not defined")
return
}
tls, _ := utils.PostPara(r, "tls")
if tls == "" {
tls = "false"
}
useTLS := (tls == "true")
rootname := ""
if eptype == "vdir" {
vdir, err := utils.PostPara(r, "rootname")
if err != nil {
utils.SendErrorResponse(w, "vdir not defined")
return
}
if !strings.HasPrefix(vdir, "/") {
vdir = "/" + vdir
}
rootname = vdir
dynamicProxyRouter.AddVirtualDirectoryProxyService(vdir, endpoint, useTLS)
} else if eptype == "subd" {
subdomain, err := utils.PostPara(r, "rootname")
if err != nil {
utils.SendErrorResponse(w, "subdomain not defined")
return
}
rootname = subdomain
dynamicProxyRouter.AddSubdomainRoutingService(subdomain, endpoint, useTLS)
} else if eptype == "root" {
rootname = "root"
dynamicProxyRouter.SetRootProxy(endpoint, useTLS)
} else {
//Invalid eptype
utils.SendErrorResponse(w, "Invalid endpoint type")
return
}
//Save it
SaveReverseProxyConfig(eptype, rootname, endpoint, useTLS)
//Update utm if exists
if uptimeMonitor != nil {
uptimeMonitor.Config.Targets = GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter)
uptimeMonitor.CleanRecords()
}
utils.SendOK(w)
}
func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
ep, err := utils.GetPara(r, "ep")
if err != nil {
utils.SendErrorResponse(w, "Invalid ep given")
}
ptype, err := utils.PostPara(r, "ptype")
if err != nil {
utils.SendErrorResponse(w, "Invalid ptype given")
}
err = dynamicProxyRouter.RemoveProxy(ptype, ep)
if err != nil {
utils.SendErrorResponse(w, err.Error())
}
RemoveReverseProxyConfig(ep)
//Update utm if exists
if uptimeMonitor != nil {
uptimeMonitor.Config.Targets = GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter)
uptimeMonitor.CleanRecords()
}
utils.SendOK(w)
}
func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
js, _ := json.Marshal(dynamicProxyRouter)
utils.SendJSONResponse(w, string(js))
}
func ReverseProxyList(w http.ResponseWriter, r *http.Request) {
eptype, err := utils.PostPara(r, "type") //Support root, vdir and subd
if err != nil {
utils.SendErrorResponse(w, "type not defined")
return
}
if eptype == "vdir" {
results := []*dynamicproxy.ProxyEndpoint{}
dynamicProxyRouter.ProxyEndpoints.Range(func(key, value interface{}) bool {
results = append(results, value.(*dynamicproxy.ProxyEndpoint))
return true
})
sort.Slice(results, func(i, j int) bool {
return results[i].Domain < results[j].Domain
})
js, _ := json.Marshal(results)
utils.SendJSONResponse(w, string(js))
} else if eptype == "subd" {
results := []*dynamicproxy.SubdomainEndpoint{}
dynamicProxyRouter.SubdomainEndpoint.Range(func(key, value interface{}) bool {
results = append(results, value.(*dynamicproxy.SubdomainEndpoint))
return true
})
sort.Slice(results, func(i, j int) bool {
return results[i].MatchingDomain < results[j].MatchingDomain
})
js, _ := json.Marshal(results)
utils.SendJSONResponse(w, string(js))
} else if eptype == "root" {
js, _ := json.Marshal(dynamicProxyRouter.Root)
utils.SendJSONResponse(w, string(js))
} else {
utils.SendErrorResponse(w, "Invalid type given")
}
}
// Handle https redirect
func HandleUpdateHttpsRedirect(w http.ResponseWriter, r *http.Request) {
useRedirect, err := utils.GetPara(r, "set")
if err != nil {
currentRedirectToHttps := false
//Load the current status
err = sysdb.Read("settings", "redirect", &currentRedirectToHttps)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
js, _ := json.Marshal(currentRedirectToHttps)
utils.SendJSONResponse(w, string(js))
} else {
if useRedirect == "true" {
sysdb.Write("settings", "redirect", true)
log.Println("Updating force HTTPS redirection to true")
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(true)
} else if useRedirect == "false" {
sysdb.Write("settings", "redirect", false)
log.Println("Updating force HTTPS redirection to false")
dynamicProxyRouter.UpdateHttpToHttpsRedirectSetting(false)
}
utils.SendOK(w)
}
}
//Handle checking if the current user is accessing via the reverse proxied interface
//Of the management interface.
func HandleManagementProxyCheck(w http.ResponseWriter, r *http.Request) {
isProxied := dynamicProxyRouter.IsProxiedSubdomain(r)
js, _ := json.Marshal(isProxied)
utils.SendJSONResponse(w, string(js))
}
// Handle incoming port set. Change the current proxy incoming port
func HandleIncomingPortSet(w http.ResponseWriter, r *http.Request) {
newIncomingPort, err := utils.PostPara(r, "incoming")
if err != nil {
utils.SendErrorResponse(w, "invalid incoming port given")
return
}
newIncomingPortInt, err := strconv.Atoi(newIncomingPort)
if err != nil {
utils.SendErrorResponse(w, "invalid incoming port given")
return
}
//Stop and change the setting of the reverse proxy service
if dynamicProxyRouter.Running {
dynamicProxyRouter.StopProxyService()
dynamicProxyRouter.Option.Port = newIncomingPortInt
dynamicProxyRouter.StartProxyService()
} else {
//Only change setting but not starting the proxy service
dynamicProxyRouter.Option.Port = newIncomingPortInt
}
sysdb.Write("settings", "inbound", newIncomingPortInt)
utils.SendOK(w)
}

View File

@ -1,32 +0,0 @@
package main
import (
"net/http"
"strings"
)
/*
router.go
This script holds the static resources router
for the reverse proxy service
*/
func AuthFsHandler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Allow access to /script/*, /img/pubic/* and /login.html without authentication
if strings.HasPrefix(r.URL.Path, "/script/") || strings.HasPrefix(r.URL.Path, "/img/public/") || r.URL.Path == "/login.html" || r.URL.Path == "/favicon.png" {
handler.ServeHTTP(w, r)
return
}
// check authentication
if !authAgent.CheckAuth(r) {
http.Redirect(w, r, "/login.html", http.StatusTemporaryRedirect)
return
}
//Authenticated
handler.ServeHTTP(w, r)
})
}

View File

@ -1,12 +0,0 @@
package main
/*
specialRouter.go
This script holds special routers
that handle special features in sub modules
*/
func injectSpecialRoutingRules() {
}

Binary file not shown.

View File

@ -1,17 +0,0 @@
github.com/boltdb/bolt,https://github.com/boltdb/bolt/blob/v1.3.1/LICENSE,MIT
github.com/gorilla/securecookie,https://github.com/gorilla/securecookie/blob/v1.1.1/LICENSE,BSD-3-Clause
github.com/gorilla/sessions,https://github.com/gorilla/sessions/blob/v1.2.1/LICENSE,BSD-3-Clause
github.com/gorilla/websocket,https://github.com/gorilla/websocket/blob/v1.4.2/LICENSE,BSD-2-Clause
github.com/oschwald/geoip2-golang,https://github.com/oschwald/geoip2-golang/blob/v1.8.0/LICENSE,ISC
github.com/oschwald/maxminddb-golang,https://github.com/oschwald/maxminddb-golang/blob/v1.10.0/LICENSE,ISC
gitlab.com/NebulousLabs/fastrand,https://gitlab.com/NebulousLabs/fastrand/blob/603482d69e40/LICENSE,MIT
gitlab.com/NebulousLabs/go-upnp,https://gitlab.com/NebulousLabs/go-upnp/blob/11da932010b6/LICENSE,MIT
gitlab.com/NebulousLabs/go-upnp/goupnp,https://gitlab.com/NebulousLabs/go-upnp/blob/11da932010b6/goupnp\LICENSE,BSD-2-Clause
golang.org/x/crypto/blake2b,https://cs.opensource.google/go/x/crypto/+/0c34fe9e:LICENSE,BSD-3-Clause
golang.org/x/net/html,https://cs.opensource.google/go/x/net/+/afb366fc:LICENSE,BSD-3-Clause
golang.org/x/sys,https://cs.opensource.google/go/x/sys/+/v0.6.0:LICENSE,BSD-3-Clause
golang.org/x/text,https://cs.opensource.google/go/x/text/+/v0.3.6:LICENSE,BSD-3-Clause
imuslab.com/zoraxy,Unknown,MIT
imuslab.com/zoraxy/mod/dynamicproxy/dpcore,Unknown,MIT
imuslab.com/zoraxy/mod/reverseproxy,Unknown,MIT
imuslab.com/zoraxy/mod/websocketproxy,Unknown,MIT
1 github.com/boltdb/bolt https://github.com/boltdb/bolt/blob/v1.3.1/LICENSE MIT
2 github.com/gorilla/securecookie https://github.com/gorilla/securecookie/blob/v1.1.1/LICENSE BSD-3-Clause
3 github.com/gorilla/sessions https://github.com/gorilla/sessions/blob/v1.2.1/LICENSE BSD-3-Clause
4 github.com/gorilla/websocket https://github.com/gorilla/websocket/blob/v1.4.2/LICENSE BSD-2-Clause
5 github.com/oschwald/geoip2-golang https://github.com/oschwald/geoip2-golang/blob/v1.8.0/LICENSE ISC
6 github.com/oschwald/maxminddb-golang https://github.com/oschwald/maxminddb-golang/blob/v1.10.0/LICENSE ISC
7 gitlab.com/NebulousLabs/fastrand https://gitlab.com/NebulousLabs/fastrand/blob/603482d69e40/LICENSE MIT
8 gitlab.com/NebulousLabs/go-upnp https://gitlab.com/NebulousLabs/go-upnp/blob/11da932010b6/LICENSE MIT
9 gitlab.com/NebulousLabs/go-upnp/goupnp https://gitlab.com/NebulousLabs/go-upnp/blob/11da932010b6/goupnp\LICENSE BSD-2-Clause
10 golang.org/x/crypto/blake2b https://cs.opensource.google/go/x/crypto/+/0c34fe9e:LICENSE BSD-3-Clause
11 golang.org/x/net/html https://cs.opensource.google/go/x/net/+/afb366fc:LICENSE BSD-3-Clause
12 golang.org/x/sys https://cs.opensource.google/go/x/sys/+/v0.6.0:LICENSE BSD-3-Clause
13 golang.org/x/text https://cs.opensource.google/go/x/text/+/v0.3.6:LICENSE BSD-3-Clause
14 imuslab.com/zoraxy Unknown MIT
15 imuslab.com/zoraxy/mod/dynamicproxy/dpcore Unknown MIT
16 imuslab.com/zoraxy/mod/reverseproxy Unknown MIT
17 imuslab.com/zoraxy/mod/websocketproxy Unknown MIT

View File

@ -1,34 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIUavNWjB6rlfRLpeXJ9TXb2FVrENYwDQYJKoZIhvcNAQEL
BQAwbjELMAkGA1UEBhMCR0wxEjAQBgNVBAgMCU1pbGt5IFdheTEOMAwGA1UEBwwF
RWFydGgxEDAOBgNVBAoMB2ltdXNsYWIxDzANBgNVBAsMBkFyb3pPUzEYMBYGA1UE
AwwPd3d3LmltdXNsYWIuY29tMB4XDTIxMDkxNzA4NTkyNFoXDTQ5MDIwMTA4NTky
NFowbjELMAkGA1UEBhMCR0wxEjAQBgNVBAgMCU1pbGt5IFdheTEOMAwGA1UEBwwF
RWFydGgxEDAOBgNVBAoMB2ltdXNsYWIxDzANBgNVBAsMBkFyb3pPUzEYMBYGA1UE
AwwPd3d3LmltdXNsYWIuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEAsBpf9ufRYOfdKft+51EibpqhA9yw6YstxL5BNselx3ETVnu7vYRIlH0ypgPN
nKguZ+BcN4mJFjQ36N4VpN7ySVfOCSCZz7lPvPfLib9iukBodBYQNAzMkKcLjyoY
gS8MD99cqe7s48k4JKp6b2WOmn2OtVZIS7AKZvVsRJNblhy7C3LkLnASKF0jb/ia
MGRAE+QV/zznvGg9FhNgQWWUil2Oesx3elj4KwlcHNX+c9pZz6yVgJrerj0s94OD
EuueiqAFOWsZrpp754ffC45PbeTNiflQ1B3aqkTtl5bL88ESgwMdtb1JGWN5HIS1
Tq2d/3PgqbtvUEhggaFDbe0OxG2V33HqEfeG3BpZpYhCB3I7FPpRC/Tp8PACY13N
HYB9P5hRU/DnINhHjMCLKxHsolhiphWuxSuNIIojRL62zj7JwjnBgcghQzVFJ4O4
TBfeMDadLII3ndDtsmR1dIba7fg+CWWdv4Zs0XGqHOaiHNclc7BhJF8SgiQxjxjm
Fh1ZsJm3LxPsw/iCl7ILE7+1aBQlBjEj0yBvMttkEDhRbILxXFPMALG/qakPvW9O
7WWClAc03ei/JFdq2camuY62/Tf1HB+TSpGWYH+cSIqsu3V5u29jmdZjrjnuM7Fz
GEjNSCsrMhSLYLkMJmrDGdFQBB31x24o9IXtyrfKZiwxMlUCAwEAAaOBhjCBgzAL
BgNVHQ8EBAMCBeAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwQAYDVR0RBDkwN4IBKoIQ
aW11c2xhYi5pbnRlcm5hbIISbm90LmZvci5wcm9kdWN0aW9uggxkZXYudXNlLm9u
bHkwHQYDVR0OBBYEFISIH/rn8RX1hcNf4rQajJR7FEdMMA0GCSqGSIb3DQEBCwUA
A4ICAQBVldF/qjWyGJ5TiZMiXly/So9zR3Xq7O1qayqYxb5SxvhZYCtsFVrAl6Ux
5bTZ0XQagjck2VouHOG6s98DpaslWFw9N8ADAmljQ8WL1hT5Ij1LXs2sF0FqttFf
YgoT5BOjnHZGlN+FgzAkdF91cYrfZwLm63jvAQtIHwjMSeymy2Fq8gdEZxagYuwG
gLkZxw1YG+gP778CKHT2Ff232kH+5up460aGLHLvg+xHQIWBt2FNGdv68u57hWxh
XXji4/DewQ0RdJW1JdpSg4npebDNiXpo9pKY/SxU056raOtPA94U/h12cHVkszT7
IxdFC2PszAblbSZhHKGE0C6SbATsqvK4gz6e4h7HWVuPPNWpPW2BNjvyenpijV/E
YsSe6F7uQE/I/iHp9VMcjWuwItqed9yKDeOfDH4+pidowbSJQ97xYfZge36ZEUHC
2ZdQsR0qS+t2h0KlEDN7FNxai3ikSB1bs2AjtU67ofGtoIz/HD70TT6zHKhISZgI
w/4/SY7Hd+P+AWSdJwo+ycZYZlXajqh/cxVJ0zVBr5vKC9KnJ+IjnQ/q7CLcxM4W
aAFC1jakdPz7qO+xNVLQRf8lVnPJNtI88OrlL4n02JlLS/QUSwELXFW0bOKP33jm
PIbPdeP8k0XVe9wlI7MzUQC8pCt+gQ77awTt83Nxp9Xdn1Zbqw==
-----END CERTIFICATE-----

View File

@ -1,52 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCwGl/259Fg590p
+37nUSJumqED3LDpiy3EvkE2x6XHcRNWe7u9hEiUfTKmA82cqC5n4Fw3iYkWNDfo
3hWk3vJJV84JIJnPuU+898uJv2K6QGh0FhA0DMyQpwuPKhiBLwwP31yp7uzjyTgk
qnpvZY6afY61VkhLsApm9WxEk1uWHLsLcuQucBIoXSNv+JowZEAT5BX/POe8aD0W
E2BBZZSKXY56zHd6WPgrCVwc1f5z2lnPrJWAmt6uPSz3g4MS656KoAU5axmumnvn
h98Ljk9t5M2J+VDUHdqqRO2XlsvzwRKDAx21vUkZY3kchLVOrZ3/c+Cpu29QSGCB
oUNt7Q7EbZXfceoR94bcGlmliEIHcjsU+lEL9Onw8AJjXc0dgH0/mFFT8Ocg2EeM
wIsrEeyiWGKmFa7FK40giiNEvrbOPsnCOcGByCFDNUUng7hMF94wNp0sgjed0O2y
ZHV0htrt+D4JZZ2/hmzRcaoc5qIc1yVzsGEkXxKCJDGPGOYWHVmwmbcvE+zD+IKX
sgsTv7VoFCUGMSPTIG8y22QQOFFsgvFcU8wAsb+pqQ+9b07tZYKUBzTd6L8kV2rZ
xqa5jrb9N/UcH5NKkZZgf5xIiqy7dXm7b2OZ1mOuOe4zsXMYSM1IKysyFItguQwm
asMZ0VAEHfXHbij0he3Kt8pmLDEyVQIDAQABAoICAATmtwUILqujyGQCu+V0PKEX
bKPO4J2fYga3xNjhdZu3afJePztnEx4O3foA4RgbFi+N7wMcsNQNYAD7LV8JVXT1
HKbkYWOGpNF9lAyhZv4IDOAuPQU11fuwqoGxij0OMie+77VLEQzF7OoYVJAFI5Lp
K6+gVyLEI4X6DqlZ8JKc+he3euJP/DFjZjkXkjMGl0H2dyZDa6+ytwCGSYeIbDnt
oKmKR0kAcOfBuu6ShiJzUUyWYRLTPJ9c1IOPBXbhV+hDy+FtOanCYvBut6Z6r3s/
gvj0F2vP6OYURQiTCdoe5YT/8TO9sOsj+Zrxlpo5+svBTd9reA2j9gulkVrd3itN
c2Ee7fyuyrCRnEcKoT6BI8/LqH5eWGQKKS9WhOz26VkrorcYYZN3g4ayv+MiiSIm
jeo/kAWCqT5ylvlw2gaCbPjB4kbx7yMI/myjgF0R4+aNQaHpXa2qqEORitGx40M7
T1V2JIxnsa83TBwumunkYC2pX7bNS0a1VuCNxUafJRKEcvKhWmiRHaWddZn46G8N
E56qFzSaLbkd+J71jso9llK5joGIQTt2pbKUdV9LIm5Nsbtp2VgF9URIw5RZFftx
PfSm9XM9DtWuxheO4gNwAuOvtaOxztNMvSkQzhTOggSRpt15hFd7CeBrpK43feAH
b2pMequB8MHpUieyxlwBAoIBAQC5IRbaKx+fSEbYeIySUwbN8GCJQl+wmvc1gqCC
DflEQqxTvCGBB5SLHHurTT0ubhXkvbrtuS5f4IC+htzKSuwlqn3lS0aOXtFP2tT6
D9iiMxLxIId5l6dD+PjMWtQcWc8wUQ7+ieRgxybDqiCWMyTbvNgwlkcIbRxmcqyN
4/LmmgzTnr5CH0DC/J7xpUJuX9LPVb4ZvBYjz5X++Yb7pCa+kXp0Z6yU48bG3sRe
yiUKp3Z4vDoOkMLHTPvTQLG81rQuJnBUw2uLWM0kg1AwteZcQ/gH1ilVbJzMBnKm
mtuJWtoPnM2zIhCsURngmBN+qxOb5kchMSvPzAQBCw7HBjWpAoIBAQDzhLQO434G
XhyDcdkdMRbDZ8Q8PqtOloAbczMuPGgwHV7rVe/BvnJS7HDDebwlJBD8nhGvgBrp
CsjNGHjSQC7ydUa8dP4Aw/46izdR8DsAwqGZq+tZhkY5CS88QpflUT5rftW0RObn
Cb/gDzdxHy35/scSICxa2HwcZnqXqfEwnbjkxFwBYFSt6hRiwNhDhd6ZxKa6gt56
DS9uIxt1IhKgXZfIw1Vo0mHHFLsB7czGZ0O24ya31Es0bUWGgWIcxvKw6MqKhFWw
ncCakVg278UYUm/zt6Dcrn3XYnK7Pr944AiKO21PMQhG7Rb+OVwxgjMhk7/BCt+k
sPR1Dct5pqrNAoIBAAl2jYp9ZdJoiWaLUvQv1ks0nFqnz+hhI33SvY2oVTOODO0C
0tubnZY20IODITt8WRYmNKXuL1arTSlwD10v0z5hpqnP3T1tz1k7oGNf5/zyi2dT
+FjYza4FzgH0Kp+AX7zih9evCMOBqpOZ4KyM1Ld+wbZKGDtwCGGcPwHJwyLSgRFY
LfWHT3IoI5/KiMjHkSkUAvGh0afm9o3gB2xZibl4CkBlBEdgFUsZHASUZKxUvxOQ
247fC3XQk5bK2csDVpZ9VISgsKCg22ugYrr6sVnKB6Wu5tH9CU7MjZPCmrI8uKTP
qRwdA6krRB1c6LIy4H+5l600rD6k+Rdsj0bRJHECggEAeBXSrRzmAsHaEb/MryaL
8SR0krjcxU5WMjMm5AAJ6OAy9J5WMxZ1TgsmuF6Jt08HyWsxkXf8zTryNqGAwz2/
aPUIQtr2fu4nqjsItrFeh0tzYVJ0JpueeXXcAz1bpkvgGiZbwB/SNdCK/DTExFX5
2DQZewi+lrX2zhKDFdNKCw1cJgPm0w7r8y9hiilK/FFBqlZdWdA7Ybiq0Qci/Som
QUqmFOyua5iDeybv6U2ZE6XMsJ1ndHON+naAOIoJFePNvguuBYyorQW9+vr9o2mt
qgbNCkRdYTXy/ImhxlB1H2hrDa+sgcbOLBuyoP8sRYXNLRutDccM7iwNAMQiuQTF
aQKCAQEAiKPwUodT6LNu4lrSbsDAYIqWwlfM0wwUhudT5UTVHSYI3ap0QOiEuzOl
IJVdx+vx7rQW7l+JIL6s4shA7mzpzuTVlhRuDuGZx0qQLP7INVpCLzIEbYGI2dL7
WLhJd4eYKltJ+BG7S51tq9/6rVcUDn5DKzyGNyeGhOnaYkk+eTm483+vpOP2/ITi
cbVv3mx4qE7zMPIxIufm+c8RonadJzYiq1uMk8t0TrcW/B9RTly/Y96kamjyU5b0
OcLdRcx3ppKAxHD9AvwAR6SiuNLfNjM9KZM40zM5goMrCJJzwgb7UGeMuw2z7L9F
+iSj2pW0Rbdy7oOcFRF/iM2GwFYc1Q==
-----END PRIVATE KEY-----

View File

@ -1,210 +0,0 @@
package main
import (
"encoding/json"
"log"
"net/http"
"net/url"
"regexp"
"strconv"
"time"
"imuslab.com/zoraxy/mod/upnp"
"imuslab.com/zoraxy/mod/utils"
)
var upnpEnabled = false
var preforwardMap map[int]string
func initUpnp() error {
go func() {
//Let UPnP discovery run in background
var err error
upnpClient, err = upnp.NewUPNPClient()
if err != nil {
log.Println("UPnP router discover error: ", err.Error())
return
}
if upnpEnabled {
//Forward all the ports
for port, policyName := range preforwardMap {
upnpClient.ForwardPort(port, policyName)
log.Println("Upnp forwarding ", port, " for "+policyName)
time.Sleep(300 * time.Millisecond)
}
}
}()
//Check if the upnp was enabled
sysdb.NewTable("upnp")
sysdb.Read("upnp", "enabled", &upnpEnabled)
//Load all the ports from database
portsMap := map[int]string{}
sysdb.Read("upnp", "portmap", &portsMap)
preforwardMap = portsMap
return nil
}
func handleUpnpDiscover(w http.ResponseWriter, r *http.Request) {
restart, err := utils.PostPara(r, "restart")
if err != nil {
type UpnpInfo struct {
ExternalIp string
RouterIp string
}
if upnpClient == nil {
utils.SendErrorResponse(w, "No UPnP router discovered")
return
}
parsedUrl, _ := url.Parse(upnpClient.Connection.Location())
ipWithPort := parsedUrl.Host
result := UpnpInfo{
ExternalIp: upnpClient.ExternalIP,
RouterIp: ipWithPort,
}
//Show if there is a upnpclient
js, _ := json.Marshal(result)
utils.SendJSONResponse(w, string(js))
} else {
if restart == "true" {
//Close the upnp client if exists
if upnpClient != nil {
saveForwardingPortsToDatabase()
upnpClient.Close()
}
//Restart a new one
initUpnp()
utils.SendOK(w)
}
}
}
func handleToggleUPnP(w http.ResponseWriter, r *http.Request) {
newMode, err := utils.PostPara(r, "mode")
if err != nil {
//Send the current mode to client side
js, _ := json.Marshal(upnpEnabled)
utils.SendJSONResponse(w, string(js))
} else {
if newMode == "true" {
upnpEnabled = true
sysdb.Read("upnp", "enabled", true)
log.Println("UPnP Enabled. Forwarding all required ports")
//Mount all Upnp requests from preforward Map
for port, policyName := range preforwardMap {
upnpClient.ForwardPort(port, policyName)
log.Println("Upnp forwarding ", port, " for "+policyName)
time.Sleep(300 * time.Millisecond)
}
utils.SendOK(w)
return
} else if newMode == "false" {
upnpEnabled = false
sysdb.Read("upnp", "enabled", false)
log.Println("UPnP disabled. Closing all forwarded ports")
//Save the current forwarded ports
saveForwardingPortsToDatabase()
//Unmount all Upnp request
for _, port := range upnpClient.RequiredPorts {
upnpClient.ClosePort(port)
log.Println("UPnP port closed: ", port)
time.Sleep(300 * time.Millisecond)
}
//done
utils.SendOK(w)
return
}
}
}
func filterRFC2141(input string) string {
rfc2141 := regexp.MustCompile(`^[\w\-.!~*'()]*(\%[\da-fA-F]{2}[\w\-.!~*'()]*)*$`)
var result []rune
for _, char := range input {
if char <= 127 && rfc2141.MatchString(string(char)) {
result = append(result, char)
}
}
return string(result)
}
func handleAddUpnpPort(w http.ResponseWriter, r *http.Request) {
portString, err := utils.PostPara(r, "port")
if err != nil {
utils.SendErrorResponse(w, "invalid port given")
return
}
portNumber, err := strconv.Atoi(portString)
if err != nil {
utils.SendErrorResponse(w, "invalid port given")
return
}
policyName, err := utils.PostPara(r, "name")
if err != nil {
utils.SendErrorResponse(w, "invalid policy name")
return
}
policyName = filterRFC2141(policyName)
err = upnpClient.ForwardPort(portNumber, policyName)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
saveForwardingPortsToDatabase()
utils.SendOK(w)
}
func handleRemoveUpnpPort(w http.ResponseWriter, r *http.Request) {
portString, err := utils.PostPara(r, "port")
if err != nil {
utils.SendErrorResponse(w, "invalid port given")
return
}
portNumber, err := strconv.Atoi(portString)
if err != nil {
utils.SendErrorResponse(w, "invalid port given")
return
}
saveForwardingPortsToDatabase()
upnpClient.ClosePort(portNumber)
}
func saveForwardingPortsToDatabase() {
//Move the sync map to map[int]string
m := make(map[int]string)
upnpClient.PolicyNames.Range(func(key, value interface{}) bool {
if k, ok := key.(int); ok {
if v, ok := value.(string); ok {
m[k] = v
}
}
return true
})
preforwardMap = m
sysdb.Write("upnp", "portmap", &preforwardMap)
}

View File

@ -1,713 +0,0 @@
<h3><i class="ui ban icon"></i> Blacklist</h3>
<p>Setup blacklist based on estimated IP geographic location or IP address</p>
<div class="ui divider"></div>
<div class="ui toggle checkbox">
<input type="checkbox" id="enableBlacklist">
<label>Enable Blacklist</label>
</div>
<div id="toggleSucc" style="float: right; display:none; color: #2abd4d;" >
<i class="ui green checkmark icon"></i> Setting Saved
</div>
<div class="ui message">
<i class="info circle icon"></i> Blacklist function require complex checking logic to validate each incoming request. Not recommend enabling this feature on servers with low end hardware.
</div>
<div class="ui divider"></div>
<h4>Country Blacklist</h4>
<p><i class="yellow exclamation triangle icon"></i>
This will block all requests from the selected country. The requester's location is estimated from their IP address and may not be 100% accurate.</p>
<div class="ui form">
<div class="field">
<label>Select Country</label>
<div id="countrySelector" class="ui fluid search selection dropdown">
<input type="hidden" name="country">
<i class="dropdown icon"></i>
<div class="default text">Select Country</div>
<div class="menu">
<div class="item" data-value="af"><i class="af flag"></i>Afghanistan</div>
<div class="item" data-value="ax"><i class="ax flag"></i>Aland Islands</div>
<div class="item" data-value="al"><i class="al flag"></i>Albania</div>
<div class="item" data-value="dz"><i class="dz flag"></i>Algeria</div>
<div class="item" data-value="as"><i class="as flag"></i>American Samoa</div>
<div class="item" data-value="ad"><i class="ad flag"></i>Andorra</div>
<div class="item" data-value="ao"><i class="ao flag"></i>Angola</div>
<div class="item" data-value="ai"><i class="ai flag"></i>Anguilla</div>
<div class="item" data-value="ag"><i class="ag flag"></i>Antigua</div>
<div class="item" data-value="ar"><i class="ar flag"></i>Argentina</div>
<div class="item" data-value="am"><i class="am flag"></i>Armenia</div>
<div class="item" data-value="aw"><i class="aw flag"></i>Aruba</div>
<div class="item" data-value="au"><i class="au flag"></i>Australia</div>
<div class="item" data-value="at"><i class="at flag"></i>Austria</div>
<div class="item" data-value="az"><i class="az flag"></i>Azerbaijan</div>
<div class="item" data-value="bs"><i class="bs flag"></i>Bahamas</div>
<div class="item" data-value="bh"><i class="bh flag"></i>Bahrain</div>
<div class="item" data-value="bd"><i class="bd flag"></i>Bangladesh</div>
<div class="item" data-value="bb"><i class="bb flag"></i>Barbados</div>
<div class="item" data-value="by"><i class="by flag"></i>Belarus</div>
<div class="item" data-value="be"><i class="be flag"></i>Belgium</div>
<div class="item" data-value="bz"><i class="bz flag"></i>Belize</div>
<div class="item" data-value="bj"><i class="bj flag"></i>Benin</div>
<div class="item" data-value="bm"><i class="bm flag"></i>Bermuda</div>
<div class="item" data-value="bt"><i class="bt flag"></i>Bhutan</div>
<div class="item" data-value="bo"><i class="bo flag"></i>Bolivia</div>
<div class="item" data-value="ba"><i class="ba flag"></i>Bosnia</div>
<div class="item" data-value="bw"><i class="bw flag"></i>Botswana</div>
<div class="item" data-value="bv"><i class="bv flag"></i>Bouvet Island</div>
<div class="item" data-value="br"><i class="br flag"></i>Brazil</div>
<div class="item" data-value="vg"><i class="vg flag"></i>British Virgin Islands</div>
<div class="item" data-value="bn"><i class="bn flag"></i>Brunei</div>
<div class="item" data-value="bg"><i class="bg flag"></i>Bulgaria</div>
<div class="item" data-value="bf"><i class="bf flag"></i>Burkina Faso</div>
<div class="item" data-value="mm"><i class="mm flag"></i>Burma</div>
<div class="item" data-value="bi"><i class="bi flag"></i>Burundi</div>
<div class="item" data-value="tc"><i class="tc flag"></i>Caicos Islands</div>
<div class="item" data-value="kh"><i class="kh flag"></i>Cambodia</div>
<div class="item" data-value="cm"><i class="cm flag"></i>Cameroon</div>
<div class="item" data-value="ca"><i class="ca flag"></i>Canada</div>
<div class="item" data-value="cv"><i class="cv flag"></i>Cape Verde</div>
<div class="item" data-value="ky"><i class="ky flag"></i>Cayman Islands</div>
<div class="item" data-value="cf"><i class="cf flag"></i>Central African Republic</div>
<div class="item" data-value="td"><i class="td flag"></i>Chad</div>
<div class="item" data-value="cl"><i class="cl flag"></i>Chile</div>
<div class="item" data-value="cn"><i class="cn flag"></i>China</div>
<div class="item" data-value="cx"><i class="cx flag"></i>Christmas Island</div>
<div class="item" data-value="cc"><i class="cc flag"></i>Cocos Islands</div>
<div class="item" data-value="co"><i class="co flag"></i>Colombia</div>
<div class="item" data-value="km"><i class="km flag"></i>Comoros</div>
<div class="item" data-value="cg"><i class="cg flag"></i>Congo Brazzaville</div>
<div class="item" data-value="cd"><i class="cd flag"></i>Congo</div>
<div class="item" data-value="ck"><i class="ck flag"></i>Cook Islands</div>
<div class="item" data-value="cr"><i class="cr flag"></i>Costa Rica</div>
<div class="item" data-value="ci"><i class="ci flag"></i>Cote Divoire</div>
<div class="item" data-value="hr"><i class="hr flag"></i>Croatia</div>
<div class="item" data-value="cu"><i class="cu flag"></i>Cuba</div>
<div class="item" data-value="cy"><i class="cy flag"></i>Cyprus</div>
<div class="item" data-value="cz"><i class="cz flag"></i>Czech Republic</div>
<div class="item" data-value="dk"><i class="dk flag"></i>Denmark</div>
<div class="item" data-value="dj"><i class="dj flag"></i>Djibouti</div>
<div class="item" data-value="dm"><i class="dm flag"></i>Dominica</div>
<div class="item" data-value="do"><i class="do flag"></i>Dominican Republic</div>
<div class="item" data-value="ec"><i class="ec flag"></i>Ecuador</div>
<div class="item" data-value="eg"><i class="eg flag"></i>Egypt</div>
<div class="item" data-value="sv"><i class="sv flag"></i>El Salvador</div>
<div class="item" data-value="gb"><i class="gb flag"></i>England</div>
<div class="item" data-value="gq"><i class="gq flag"></i>Equatorial Guinea</div>
<div class="item" data-value="er"><i class="er flag"></i>Eritrea</div>
<div class="item" data-value="ee"><i class="ee flag"></i>Estonia</div>
<div class="item" data-value="et"><i class="et flag"></i>Ethiopia</div>
<div class="item" data-value="eu"><i class="eu flag"></i>European Union</div>
<div class="item" data-value="fk"><i class="fk flag"></i>Falkland Islands</div>
<div class="item" data-value="fo"><i class="fo flag"></i>Faroe Islands</div>
<div class="item" data-value="fj"><i class="fj flag"></i>Fiji</div>
<div class="item" data-value="fi"><i class="fi flag"></i>Finland</div>
<div class="item" data-value="fr"><i class="fr flag"></i>France</div>
<div class="item" data-value="gf"><i class="gf flag"></i>French Guiana</div>
<div class="item" data-value="pf"><i class="pf flag"></i>French Polynesia</div>
<div class="item" data-value="tf"><i class="tf flag"></i>French Territories</div>
<div class="item" data-value="ga"><i class="ga flag"></i>Gabon</div>
<div class="item" data-value="gm"><i class="gm flag"></i>Gambia</div>
<div class="item" data-value="ge"><i class="ge flag"></i>Georgia</div>
<div class="item" data-value="de"><i class="de flag"></i>Germany</div>
<div class="item" data-value="gh"><i class="gh flag"></i>Ghana</div>
<div class="item" data-value="gi"><i class="gi flag"></i>Gibraltar</div>
<div class="item" data-value="gr"><i class="gr flag"></i>Greece</div>
<div class="item" data-value="gl"><i class="gl flag"></i>Greenland</div>
<div class="item" data-value="gd"><i class="gd flag"></i>Grenada</div>
<div class="item" data-value="gp"><i class="gp flag"></i>Guadeloupe</div>
<div class="item" data-value="gu"><i class="gu flag"></i>Guam</div>
<div class="item" data-value="gt"><i class="gt flag"></i>Guatemala</div>
<div class="item" data-value="gw"><i class="gw flag"></i>Guinea-Bissau</div>
<div class="item" data-value="gn"><i class="gn flag"></i>Guinea</div>
<div class="item" data-value="gy"><i class="gy flag"></i>Guyana</div>
<div class="item" data-value="ht"><i class="ht flag"></i>Haiti</div>
<div class="item" data-value="hm"><i class="hm flag"></i>Heard Island</div>
<div class="item" data-value="hn"><i class="hn flag"></i>Honduras</div>
<div class="item" data-value="hk"><i class="hk flag"></i>Hong Kong</div>
<div class="item" data-value="hu"><i class="hu flag"></i>Hungary</div>
<div class="item" data-value="is"><i class="is flag"></i>Iceland</div>
<div class="item" data-value="in"><i class="in flag"></i>India</div>
<div class="item" data-value="io"><i class="io flag"></i>Indian Ocean Territory</div>
<div class="item" data-value="id"><i class="id flag"></i>Indonesia</div>
<div class="item" data-value="ir"><i class="ir flag"></i>Iran</div>
<div class="item" data-value="iq"><i class="iq flag"></i>Iraq</div>
<div class="item" data-value="ie"><i class="ie flag"></i>Ireland</div>
<div class="item" data-value="il"><i class="il flag"></i>Israel</div>
<div class="item" data-value="it"><i class="it flag"></i>Italy</div>
<div class="item" data-value="jm"><i class="jm flag"></i>Jamaica</div>
<div class="item" data-value="jp"><i class="jp flag"></i>Japan</div>
<div class="item" data-value="jo"><i class="jo flag"></i>Jordan</div>
<div class="item" data-value="kz"><i class="kz flag"></i>Kazakhstan</div>
<div class="item" data-value="ke"><i class="ke flag"></i>Kenya</div>
<div class="item" data-value="ki"><i class="ki flag"></i>Kiribati</div>
<div class="item" data-value="kw"><i class="kw flag"></i>Kuwait</div>
<div class="item" data-value="kg"><i class="kg flag"></i>Kyrgyzstan</div>
<div class="item" data-value="la"><i class="la flag"></i>Laos</div>
<div class="item" data-value="lv"><i class="lv flag"></i>Latvia</div>
<div class="item" data-value="lb"><i class="lb flag"></i>Lebanon</div>
<div class="item" data-value="ls"><i class="ls flag"></i>Lesotho</div>
<div class="item" data-value="lr"><i class="lr flag"></i>Liberia</div>
<div class="item" data-value="ly"><i class="ly flag"></i>Libya</div>
<div class="item" data-value="li"><i class="li flag"></i>Liechtenstein</div>
<div class="item" data-value="lt"><i class="lt flag"></i>Lithuania</div>
<div class="item" data-value="lu"><i class="lu flag"></i>Luxembourg</div>
<div class="item" data-value="mo"><i class="mo flag"></i>Macau</div>
<div class="item" data-value="mk"><i class="mk flag"></i>Macedonia</div>
<div class="item" data-value="mg"><i class="mg flag"></i>Madagascar</div>
<div class="item" data-value="mw"><i class="mw flag"></i>Malawi</div>
<div class="item" data-value="my"><i class="my flag"></i>Malaysia</div>
<div class="item" data-value="mv"><i class="mv flag"></i>Maldives</div>
<div class="item" data-value="ml"><i class="ml flag"></i>Mali</div>
<div class="item" data-value="mt"><i class="mt flag"></i>Malta</div>
<div class="item" data-value="mh"><i class="mh flag"></i>Marshall Islands</div>
<div class="item" data-value="mq"><i class="mq flag"></i>Martinique</div>
<div class="item" data-value="mr"><i class="mr flag"></i>Mauritania</div>
<div class="item" data-value="mu"><i class="mu flag"></i>Mauritius</div>
<div class="item" data-value="yt"><i class="yt flag"></i>Mayotte</div>
<div class="item" data-value="mx"><i class="mx flag"></i>Mexico</div>
<div class="item" data-value="fm"><i class="fm flag"></i>Micronesia</div>
<div class="item" data-value="md"><i class="md flag"></i>Moldova</div>
<div class="item" data-value="mc"><i class="mc flag"></i>Monaco</div>
<div class="item" data-value="mn"><i class="mn flag"></i>Mongolia</div>
<div class="item" data-value="me"><i class="me flag"></i>Montenegro</div>
<div class="item" data-value="ms"><i class="ms flag"></i>Montserrat</div>
<div class="item" data-value="ma"><i class="ma flag"></i>Morocco</div>
<div class="item" data-value="mz"><i class="mz flag"></i>Mozambique</div>
<div class="item" data-value="na"><i class="na flag"></i>Namibia</div>
<div class="item" data-value="nr"><i class="nr flag"></i>Nauru</div>
<div class="item" data-value="np"><i class="np flag"></i>Nepal</div>
<div class="item" data-value="an"><i class="an flag"></i>Netherlands Antilles</div>
<div class="item" data-value="nl"><i class="nl flag"></i>Netherlands</div>
<div class="item" data-value="nc"><i class="nc flag"></i>New Caledonia</div>
<div class="item" data-value="pg"><i class="pg flag"></i>New Guinea</div>
<div class="item" data-value="nz"><i class="nz flag"></i>New Zealand</div>
<div class="item" data-value="ni"><i class="ni flag"></i>Nicaragua</div>
<div class="item" data-value="ne"><i class="ne flag"></i>Niger</div>
<div class="item" data-value="ng"><i class="ng flag"></i>Nigeria</div>
<div class="item" data-value="nu"><i class="nu flag"></i>Niue</div>
<div class="item" data-value="nf"><i class="nf flag"></i>Norfolk Island</div>
<div class="item" data-value="kp"><i class="kp flag"></i>North Korea</div>
<div class="item" data-value="mp"><i class="mp flag"></i>Northern Mariana Islands</div>
<div class="item" data-value="no"><i class="no flag"></i>Norway</div>
<div class="item" data-value="om"><i class="om flag"></i>Oman</div>
<div class="item" data-value="pk"><i class="pk flag"></i>Pakistan</div>
<div class="item" data-value="pw"><i class="pw flag"></i>Palau</div>
<div class="item" data-value="ps"><i class="ps flag"></i>Palestine</div>
<div class="item" data-value="pa"><i class="pa flag"></i>Panama</div>
<div class="item" data-value="py"><i class="py flag"></i>Paraguay</div>
<div class="item" data-value="pe"><i class="pe flag"></i>Peru</div>
<div class="item" data-value="ph"><i class="ph flag"></i>Philippines</div>
<div class="item" data-value="pn"><i class="pn flag"></i>Pitcairn Islands</div>
<div class="item" data-value="pl"><i class="pl flag"></i>Poland</div>
<div class="item" data-value="pt"><i class="pt flag"></i>Portugal</div>
<div class="item" data-value="pr"><i class="pr flag"></i>Puerto Rico</div>
<div class="item" data-value="qa"><i class="qa flag"></i>Qatar</div>
<div class="item" data-value="re"><i class="re flag"></i>Reunion</div>
<div class="item" data-value="ro"><i class="ro flag"></i>Romania</div>
<div class="item" data-value="ru"><i class="ru flag"></i>Russia</div>
<div class="item" data-value="rw"><i class="rw flag"></i>Rwanda</div>
<div class="item" data-value="sh"><i class="sh flag"></i>Saint Helena</div>
<div class="item" data-value="kn"><i class="kn flag"></i>Saint Kitts and Nevis</div>
<div class="item" data-value="lc"><i class="lc flag"></i>Saint Lucia</div>
<div class="item" data-value="pm"><i class="pm flag"></i>Saint Pierre</div>
<div class="item" data-value="vc"><i class="vc flag"></i>Saint Vincent</div>
<div class="item" data-value="ws"><i class="ws flag"></i>Samoa</div>
<div class="item" data-value="sm"><i class="sm flag"></i>San Marino</div>
<div class="item" data-value="gs"><i class="gs flag"></i>Sandwich Islands</div>
<div class="item" data-value="st"><i class="st flag"></i>Sao Tome</div>
<div class="item" data-value="sa"><i class="sa flag"></i>Saudi Arabia</div>
<div class="item" data-value="sn"><i class="sn flag"></i>Senegal</div>
<div class="item" data-value="cs"><i class="cs flag"></i>Serbia</div>
<div class="item" data-value="rs"><i class="rs flag"></i>Serbia</div>
<div class="item" data-value="sc"><i class="sc flag"></i>Seychelles</div>
<div class="item" data-value="sl"><i class="sl flag"></i>Sierra Leone</div>
<div class="item" data-value="sg"><i class="sg flag"></i>Singapore</div>
<div class="item" data-value="sk"><i class="sk flag"></i>Slovakia</div>
<div class="item" data-value="si"><i class="si flag"></i>Slovenia</div>
<div class="item" data-value="sb"><i class="sb flag"></i>Solomon Islands</div>
<div class="item" data-value="so"><i class="so flag"></i>Somalia</div>
<div class="item" data-value="za"><i class="za flag"></i>South Africa</div>
<div class="item" data-value="kr"><i class="kr flag"></i>South Korea</div>
<div class="item" data-value="es"><i class="es flag"></i>Spain</div>
<div class="item" data-value="lk"><i class="lk flag"></i>Sri Lanka</div>
<div class="item" data-value="sd"><i class="sd flag"></i>Sudan</div>
<div class="item" data-value="sr"><i class="sr flag"></i>Suriname</div>
<div class="item" data-value="sj"><i class="sj flag"></i>Svalbard</div>
<div class="item" data-value="sz"><i class="sz flag"></i>Swaziland</div>
<div class="item" data-value="se"><i class="se flag"></i>Sweden</div>
<div class="item" data-value="ch"><i class="ch flag"></i>Switzerland</div>
<div class="item" data-value="sy"><i class="sy flag"></i>Syria</div>
<div class="item" data-value="tw"><i class="tw flag"></i>Taiwan</div>
<div class="item" data-value="tj"><i class="tj flag"></i>Tajikistan</div>
<div class="item" data-value="tz"><i class="tz flag"></i>Tanzania</div>
<div class="item" data-value="th"><i class="th flag"></i>Thailand</div>
<div class="item" data-value="tl"><i class="tl flag"></i>Timorleste</div>
<div class="item" data-value="tg"><i class="tg flag"></i>Togo</div>
<div class="item" data-value="tk"><i class="tk flag"></i>Tokelau</div>
<div class="item" data-value="to"><i class="to flag"></i>Tonga</div>
<div class="item" data-value="tt"><i class="tt flag"></i>Trinidad</div>
<div class="item" data-value="tn"><i class="tn flag"></i>Tunisia</div>
<div class="item" data-value="tr"><i class="tr flag"></i>Turkey</div>
<div class="item" data-value="tm"><i class="tm flag"></i>Turkmenistan</div>
<div class="item" data-value="tv"><i class="tv flag"></i>Tuvalu</div>
<div class="item" data-value="ug"><i class="ug flag"></i>Uganda</div>
<div class="item" data-value="ua"><i class="ua flag"></i>Ukraine</div>
<div class="item" data-value="ae"><i class="ae flag"></i>United Arab Emirates</div>
<div class="item" data-value="us"><i class="us flag"></i>United States</div>
<div class="item" data-value="uy"><i class="uy flag"></i>Uruguay</div>
<div class="item" data-value="um"><i class="um flag"></i>Us Minor Islands</div>
<div class="item" data-value="vi"><i class="vi flag"></i>Us Virgin Islands</div>
<div class="item" data-value="uz"><i class="uz flag"></i>Uzbekistan</div>
<div class="item" data-value="vu"><i class="vu flag"></i>Vanuatu</div>
<div class="item" data-value="va"><i class="va flag"></i>Vatican City</div>
<div class="item" data-value="ve"><i class="ve flag"></i>Venezuela</div>
<div class="item" data-value="vn"><i class="vn flag"></i>Vietnam</div>
<div class="item" data-value="wf"><i class="wf flag"></i>Wallis and Futuna</div>
<div class="item" data-value="eh"><i class="eh flag"></i>Western Sahara</div>
<div class="item" data-value="ye"><i class="ye flag"></i>Yemen</div>
<div class="item" data-value="zm"><i class="zm flag"></i>Zambia</div>
<div class="item" data-value="zw"><i class="zw flag"></i>Zimbabwe</div>
</div>
</div>
</div>
<button class="ui basic red button" id="ban-btn" onclick="addCountryToBlacklist();"><i class="ui red ban icon"></i> Blacklist Country</button>
</div>
<table class="ui unstackable basic celled table">
<thead>
<tr>
<th>ISO Code</th>
<th>Remove</th>
</tr>
</thead>
<tbody id="banned-list">
</tbody>
</table>
<div class="ui divider"></div>
<h4>IP Blacklist</h4>
<p>Black a certain IP or IP range</p>
<div class="ui form">
<div class="field">
<label>IP Address</label>
<input id="ipAddressInput" type="text" placeholder="IP Address">
</div>
<button id="addIpButton" onclick="addIpBlacklist();" class="ui basic red icon button">
<i class="ban icon"></i> Blacklist IP
</button>
</div>
<div class="ui message">
<i class="ui info circle icon"></i> IP Address support the following formats
<div class="ui bulleted list">
<div class="item">Fixed IP Address (e.g. 192.128.4.100)</div>
<div class="item">IP Wildcard (e.g. 172.164.*.*)</div>
<div class="item">CIDR String (e.g. 128.32.0.1/16)</div>
</div>
</div>
<table class="ui unstackable basic celled table">
<thead>
<tr>
<th>IP Address</th>
<th>Remove</th>
</tr>
</thead>
<tbody id="blacklistIpTable">
</tbody>
</table>
<div class="ui divider"></div>
<h4>Visitor IP list</h4>
<button style="margin-top: -1em;" onclick="initBlacklistQuickBanTable();" class="ui green small right floated circular basic icon button"><i class="ui refresh icon"></i></button>
<p>Observe strange traffic on your sites? Ban them in the list below.</p>
<table class="ui celled unstackable table" id="ipTable">
<thead>
<tr>
<th>IP</th>
<th>Access Count</th>
<th>Blacklist</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="pagination"></div>
<script>
$(".dropdown").dropdown();
function filterCountries(codesToShow) {
// get all items in the dropdown
const items = document.querySelectorAll('.ui.fluid.search.selection.dropdown .menu .item');
// loop through all items
items.forEach(item => {
// get the value of the item (i.e. the country code)
const code = item.dataset.value;
// if the code is in the array of codes to show, show the item
if (codesToShow.includes(code)) {
item.style.display = 'none';
}
// otherwise, hide the item
else {
item.style.display = 'block';
}
});
}
//Get the country name from the dropdown
function getCountryName(countryCode) {
return $('#countrySelector .item[data-value="' + countryCode.toLowerCase() + '"]').text().trim();
}
function initBannedCountryList(){
$.get("/api/blacklist/list?type=country", function(data) {
let bannedListHtml = '';
data.forEach((countryCode) => {
bannedListHtml += `
<tr>
<td><i class="${countryCode} flag"></i> ${getCountryName(countryCode)} (${countryCode.toUpperCase()})</td>
<td><button class="ui red basic mini icon button" onclick="removeFromBannedList('${countryCode}')"><i class="trash icon"></i></button></td>
</tr>
`;
});
$('#banned-list').html(bannedListHtml);
filterCountries(data);
if (data.length === 0) {
$('#banned-list').append(`
<tr>
<td colspan="2">
<i class="green check circle icon"></i>
There are no blacklisted countries
</td>
</tr>
`);
}
});
}
initBannedCountryList();
function addCountryToBlacklist() {
var countryCode = $("#countrySelector").dropdown("get value").toLowerCase();
$('#countrySelector').dropdown('clear');
$.ajax({
type: "POST",
url: "/api/blacklist/country/add",
data: { cc: countryCode },
success: function(response) {
if (response.error != undefined){
alert(response.error);
}
initBannedCountryList();
},
error: function(xhr, status, error) {
// handle error response
}
});
}
function removeFromBannedList(countryCode){
if (confirm("Confirm removing " + getCountryName(countryCode) + " from blacklist?")){
countryCode = countryCode.toLowerCase();
$.ajax({
url: "/api/blacklist/country/remove",
method: "POST",
data: { cc: countryCode },
success: function(response) {
if (response.error != undefined){
alert(response.error);
}
initBannedCountryList();
},
error: function(xhr, status, error) {
console.error("Error removing country from blacklist: " + error);
// Handle error response
}
});
}
}
function initIpBanTable(){
$.get('/api/blacklist/list?type=ip', function(data) {
$('#blacklistIpTable').html("");
if (data.length === 0) {
$('#blacklistIpTable').append(`
<tr>
<td colspan="2"><i class="green check circle icon"></i>There are no blacklisted IP addresses</td>
</tr>
`);
} else {
$.each(data, function(index, ip) {
let icon = "globe icon";
if (isLAN(ip)){
icon = "desktop icon";
}else if (isHomeAddr(ip)){
icon = "home icon";
}
$('#blacklistIpTable').append(`
<tr class="blacklistItem" ip="${encodeURIComponent(ip)}">
<td><i class="${icon}"></i> ${ip}</td>
<td><button class="ui icon basic mini red button" onclick="removeIpBlacklist('${ip}');"><i class="trash alternate icon"></i></button></td>
</tr>
`);
});
}
initBlacklistQuickBanTable();
});
}
initIpBanTable();
//Check if a input is a valid IP address, wildcard of a IP address or a CIDR string
function isValidIpFilter(input) {
// Check if input is a valid IP address
const isValidIp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/.test(input);
if (isValidIp) {
return true;
}
// Check if input is a wildcard IP address
const isValidWildcardIp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])$/.test(input);
if (isValidWildcardIp) {
return true;
}
// Check if input is a valid CIDR address string
const isValidCidr = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\/([0-9]|[1-2][0-9]|3[0-2])$/.test(input);
if (isValidCidr) {
return true;
}
// Input is not a valid IP address, wildcard IP address, or CIDR address string
return false;
}
$("#ipAddressInput").on("input", function() {
$(this).val($(this).val().trim());
var ipAddress = $(this).val();
if (!isValidIpFilter(ipAddress)) {
$(this).parent().addClass("error");
} else {
$(this).parent().removeClass("error");
}
});
function isLAN(ipAddress) {
function ip2long(ipAddress) {
// Convert the IP address to a 32-bit integer
const parts = ipAddress.split(".");
return (
(parseInt(parts[0]) << 24) |
(parseInt(parts[1]) << 16) |
(parseInt(parts[2]) << 8) |
parseInt(parts[3])
);
}
// Define the LAN IP address ranges
const LAN_RANGES = [
{ start: "10.0.0.0", end: "10.255.255.255" },
{ start: "172.16.0.0", end: "172.31.255.255" },
{ start: "192.168.0.0", end: "192.168.255.255" }
];
// Check if the IP address is within any of the LAN ranges
for (let i = 0; i < LAN_RANGES.length; i++) {
const rangeStart = ip2long(LAN_RANGES[i].start);
const rangeEnd = ip2long(LAN_RANGES[i].end);
const ipAddressLong = ip2long(ipAddress);
if (ipAddressLong >= rangeStart && ipAddressLong <= rangeEnd) {
return true;
}
}
return false;
}
function isHomeAddr(ipAddress) {
const specialIpAddresses = ['0.0.0.0', '127.0.0.1', '::1'];
return specialIpAddresses.includes(ipAddress);
}
function addIpBlacklist(){
let targetIp = $("#ipAddressInput").val().trim();
if (targetIp == ""){
alert("IP address is empty")
return
}
if (!isValidIpFilter(targetIp)){
if (!confirm("This doesn't seems like a valid IP address. Continue anyway?")){
return;
}
}
$.ajax({
url: "/api/blacklist/ip/add",
type: "POST",
data: {ip: targetIp.toLowerCase()},
success: function(response) {
if (response.error !== undefined) {
alert(response.error);
} else {
initIpBanTable();
}
$("#ipAddressInput").val("");
$("#ipAddressInput").parent().remvoeClass("error");
},
error: function() {
alert("Failed to add IP address to blacklist.");
}
});
}
function removeIpBlacklist(ipaddr){
if (confirm("Confirm remove blacklist for " + ipaddr + " ?")){
$.ajax({
url: "/api/blacklist/ip/remove",
type: "POST",
data: {ip: ipaddr.toLowerCase()},
success: function(response) {
if (response.error !== undefined) {
alert(response.error);
} else {
initIpBanTable();
}
},
error: function() {
alert("Failed to remove IP address from blacklist.");
}
});
}
}
//function to check for blacklist enable
function enableBlacklist() {
var isChecked = $('#enableBlacklist').is(':checked');
$.ajax({
type: 'POST',
url: '/api/blacklist/enable',
data: { enable: isChecked },
success: function(data){
$("#toggleSucc").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast");
}
});
}
function initBlacklistEnableState(){
$.get('/api/blacklist/enable', function(data){
if (data == true){
$('#enableBlacklist').parent().checkbox("set checked");
}
//Register on change event
$("#enableBlacklist").on("change", function(){
enableBlacklist();
})
});
}
initBlacklistEnableState();
//Load the summary to ip access table
function initBlacklistQuickBanTable(){
$.get("/api/stats/summary", function(data){
initIpAccessTable(data.RequestClientIp);
})
}
initBlacklistQuickBanTable();
var blacklist_entriesPerPage = 30;
var blacklist_currentPage = 1;
var blacklist_totalPages = 0;
function initIpAccessTable(ipAccessCounts){
blacklist_totalPages = Math.ceil(Object.keys(ipAccessCounts).length / blacklist_entriesPerPage);
function sortkv(obj) {
var sortable = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
sortable.push([key, obj[key]]);
}
}
sortable.sort(function(a, b) {
return b[1] - a[1];
});
var sortedObj = {};
sortable.forEach(function(item) {
sortedObj[item[0]] = item[1];
});
return sortedObj;
}
ipAccessCounts = sortkv(ipAccessCounts);
function renderTable() {
var tableBody = $("#ipTable tbody");
tableBody.empty();
var startIndex = (blacklist_currentPage - 1) * blacklist_entriesPerPage;
var endIndex = startIndex + blacklist_entriesPerPage;
var slicedEntries = Object.entries(ipAccessCounts).slice(startIndex, endIndex);
slicedEntries.forEach(function([ip, accessCount]) {
var row = $("<tr>").appendTo(tableBody);
$("<td>").text(ip).appendTo(row);
$("<td>").text(accessCount).appendTo(row);
if (ipInBlacklist(ip)){
$("<td>").html(`<button class="ui basic green small icon button" title"Unban IP" onclick="handleUnban('${ip}');"><i class="green check icon"></i></button>`).appendTo(row);
}else{
$("<td>").html(`<button class="ui basic red small icon button" title"Ban IP" onclick="handleBanIp('${ip}');"><i class="red ban icon"></i></button>`).appendTo(row);
}
});
}
function renderPagination() {
var paginationDiv = $(".pagination");
paginationDiv.empty();
for (var i = 1; i <= blacklist_totalPages; i++) {
var button = $("<button>").text(i).addClass("ui small basic compact button");
if (i === blacklist_currentPage) {
button.addClass("disabled");
}
button.click(function() {
blacklist_currentPage = parseInt($(this).text());
renderTable();
renderPagination();
});
button.appendTo(paginationDiv);
}
}
renderTable();
renderPagination();
}
function ipInBlacklist(targetIp){
let inBlacklist = false;
$(".blacklistItem").each(function(){
if ($(this).attr("ip") == encodeURIComponent(targetIp)){
inBlacklist = true;
}
});
return inBlacklist;
}
function handleBanIp(targetIp){
$("#ipAddressInput").val(targetIp);
addIpBlacklist();
}
function handleUnban(targetIp){
removeIpBlacklist(targetIp);
}
</script>

View File

@ -1,279 +0,0 @@
<h3><i class="ui lock icon"></i> TLS / SSL Certificates</h3>
<p>Setup TLS cert for different domains of your reverse proxy server names</p>
<div class="ui divider"></div>
<h4>Default Certificates</h4>
<small>When there are no matching certificate for the requested server name, reverse proxy router will always fallback to this one.<br>Note that you need both of them uploaded for it to fallback properly</small></p>
<table class="ui very basic celled table">
<thead>
<tr><th>Key Type</th>
<th>Exists</th>
</tr></thead>
<tbody>
<tr>
<td><i class="globe icon"></i> Default Public Key</td>
<td id="pubkeyExists"></td>
</tr>
<tr>
<td><i class="lock icon"></i> Default Private Key</td>
<td id="prikeyExists"></td>
</tr>
</tbody>
</table>
<button class="ui button" onclick="uploadPublicKey();"><i class="globe icon"></i> Upload Public Key</button>
<button class="ui black button" onclick="uploadPrivateKey();"><i class="lock icon"></i> Upload Private Key</button>
<div class="ui divider"></div>
<h4>Sub-domain Certificates</h4>
<p>Provide certificates for multiple domains reverse proxy</p>
<div class="ui fluid form">
<div class="three fields">
<div class="field">
<label>Server Name (Domain)</label>
<input type="text" id="certdomain" placeholder="example.com / blog.example.com">
</div>
<div class="field">
<label>Public Key</label>
<input type="file" id="pubkeySelector" onchange="handleFileSelect(event, 'pub')">
</div>
<div class="field">
<label>Private Key</label>
<input type="file" id="prikeySelector" onchange="handleFileSelect(event, 'pri')">
</div>
</div>
<button class="ui teal button" onclick="handleDomainUploadByKeypress();"><i class="ui upload icon"></i> Upload</button>
</div>
<div id="certUploadSuccMsg" class="ui green message" style="display:none;">
<i class="ui checkmark icon"></i> Certificate for domain <span id="certUploadingDomain"></span> uploaded.
</div>
<br>
<div>
<table class="ui sortable unstackable celled table">
<thead>
<tr><th>Domain</th>
<th>Last Update</th>
<th class="no-sort">Remove</th>
</tr></thead>
<tbody id="certifiedDomainList">
</tbody>
</table>
<button class="ui green basic button" onclick="initManagedDomainCertificateList();"><i class="refresh icon"></i> Refresh List</button>
</div>
<div class="ui message">
<h4><i class="info circle icon"></i> Sub-domain Certificates</h4>
If you have 3rd or even 4th level subdomains like <code>blog.example.com</code> or <code>en.blog.example.com</code> ,
depending on your certificates coverage, you might need to setup them one by one (i.e. having two seperate certificate for <code>a.example.com</code> and <code>b.example.com</code>).<br>
If you have a wildcard certificate that covers <code>*.example.com</code>, you can just enter <code>example.com</code> as server name in the form below to add a certificate.
</div>
<script>
var uploadPendingPublicKey = undefined;
var uploadPendingPrivateKey = undefined;
//Delete the certificate by its domain
function deleteCertificate(domain){
if (confirm("Confirm delete certificate for " + domain + " ?")){
$.ajax({
url: "/api/cert/delete",
method: "POST",
data: {domain: domain},
success: function(data){
if (data.error != undefined){
alert(data.error);
}else{
initManagedDomainCertificateList();
}
}
});
}
}
//List the stored certificates
function initManagedDomainCertificateList(){
$("#certifiedDomainList").html("");
$.get("/api/cert/list?date=true", function(data){
if (data.error != undefined){
alert(data.error);
}else{
data.forEach(entry => {
$("#certifiedDomainList").append(`<tr>
<td>${entry.Domain}</td>
<td>${entry.LastModifiedDate}</td>
<td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
</tr>`);
})
}
})
}
initManagedDomainCertificateList();
function handleDomainUploadByKeypress(){
handleDomainKeysUpload(function(){
$("#certUploadingDomain").text($("#certdomain").val().trim());
//After uploaded, reset the file selector
document.getElementById('pubkeySelector').value = '';
document.getElementById('prikeySelector').value = '';
document.getElementById('certdomain').value = '';
uploadPendingPublicKey = undefined;
uploadPendingPrivateKey = undefined;
//Show succ
$("#certUploadSuccMsg").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
initManagedDomainCertificateList();
});
}
//Handle domain keys upload
function handleDomainKeysUpload(callback=undefined){
let domain = $("#certdomain").val();
if (domain.trim() == ""){
alert("Missing domain.");
return;
}
if (uploadPendingPublicKey && uploadPendingPrivateKey && typeof uploadPendingPublicKey === 'object' && typeof uploadPendingPrivateKey === 'object') {
const publicKeyForm = new FormData();
publicKeyForm.append('file', uploadPendingPublicKey, 'publicKey');
const privateKeyForm = new FormData();
privateKeyForm.append('file', uploadPendingPrivateKey, 'privateKey');
const publicKeyRequest = new XMLHttpRequest();
publicKeyRequest.open('POST', '/api/cert/upload?ktype=pub&domain=' + domain);
publicKeyRequest.onreadystatechange = function() {
if (publicKeyRequest.readyState === XMLHttpRequest.DONE) {
if (publicKeyRequest.status !== 200) {
alert('Error uploading public key: ' + publicKeyRequest.statusText);
}
if (callback != undefined){
callback();
}
}
};
publicKeyRequest.send(publicKeyForm);
const privateKeyRequest = new XMLHttpRequest();
privateKeyRequest.open('POST', '/api/cert/upload?ktype=pri&domain=' + domain);
privateKeyRequest.onreadystatechange = function() {
if (privateKeyRequest.readyState === XMLHttpRequest.DONE) {
if (privateKeyRequest.status !== 200) {
alert('Error uploading private key: ' + privateKeyRequest.statusText);
}
if (callback != undefined){
callback();
}
}
};
privateKeyRequest.send(privateKeyForm);
} else {
alert('One or both of the files is missing or not a file object');
}
}
//Handlers for selecting domain based key pairs
//ktype = {"pub" / "pri"}
function handleFileSelect(event, ktype="pub") {
const file = event.target.files[0];
//const fileNameInput = document.getElementById('selected-file-name');
if (ktype == "pub"){
uploadPendingPublicKey = file;
}else if (ktype == "pri"){
uploadPendingPrivateKey = file;
}
//fileNameInput.value = file.name;
}
//Check if the default keypairs exists
function initDefaultKeypairCheck(){
$.get("/api/cert/checkDefault", function(data){
let tick = `<i class="ui green checkmark icon"></i>`;
let cross = `<i class="ui red times icon"></i>`;
$("#pubkeyExists").html(data.DefaultPubExists?tick:cross);
$("#prikeyExists").html(data.DefaultPriExists?tick:cross);
});
}
initDefaultKeypairCheck();
function uploadPrivateKey(){
// create file input element
const input = document.createElement('input');
input.type = 'file';
// add change listener to file input
input.addEventListener('change', () => {
// create form data object
const formData = new FormData();
// add selected file to form data
formData.append('file', input.files[0]);
// send form data to server
fetch('/api/cert/upload?ktype=pri', {
method: 'POST',
body: formData
})
.then(response => {
initDefaultKeypairCheck();
if (response.ok) {
alert('File upload successful!');
} else {
response.text().then(text => {
alert(text);
});
//console.log(response.text());
//alert('File upload failed!');
}
})
.catch(error => {
alert('An error occurred while uploading the file.');
console.error(error);
});
});
// click file input to open file selector
input.click();
}
function uploadPublicKey() {
// create file input element
const input = document.createElement('input');
input.type = 'file';
// add change listener to file input
input.addEventListener('change', () => {
// create form data object
const formData = new FormData();
// add selected file to form data
formData.append('file', input.files[0]);
// send form data to server
fetch('/api/cert/upload?ktype=pub', {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
alert('File upload successful!');
initDefaultKeypairCheck();
} else {
response.text().then(text => {
alert(text);
});
//console.log(response.text());
//alert('File upload failed!');
}
})
.catch(error => {
alert('An error occurred while uploading the file.');
console.error(error);
});
});
// click file input to open file selector
input.click();
}
</script>

View File

@ -1,160 +0,0 @@
<h3><i class="level up alternate icon"></i> Redirection Rules</h3>
<p>Add exception case for redirecting any matching URLs</p>
<div class="ui basic segment">
<div style="width: 100%; overflow-x: auto;">
<table class="ui sortable unstackable celled table" >
<thead>
<tr>
<th>Redirection URL</th>
<th class="no-sort"></th>
<th>Destination URL</th>
<th class="no-sort">Copy Pathname</th>
<th class="no-sort">Status Code</th>
<th class="no-sort">Remove</th>
</tr>
</thead>
<tbody id="redirectionRuleList">
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="ui green message" id="delRuleSucc" style="display:none;">
<i class="ui green checkmark icon"></i> Redirection Rule Deleted
</div>
</div>
<div class="ui divider"></div>
<p>Add path redirection to your domain</p>
<div class="ui divider"></div>
<div class="ui form">
<div class="field">
<label>Redirection URL</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>
</div>
<div class="field">
<label>Destination URL</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</small>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="forward-childpath" tabindex="0" class="hidden" checked>
<label>Forward Pathname</label>
</div>
<div class="ui message">
<p>Append the current pathname after the redirect destination</p>
<i class="check square outline icon"></i> old.example.com<b>/blog?post=13</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com<b>/blog?post=13</b> <br>
<i class="square outline icon"></i> Disabled old.example.com<b>/blog?post=13</b> <i class="long arrow alternate right icon" style="margin-left: 1em;"></i> new.example.com
</div>
</div>
<div class="grouped fields">
<label>Redirection Status Code</label>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="redirect-type" value="307" checked>
<label>Temporary Redirect <br><small>Status Code: 307</small></label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="redirect-type" value="301">
<label>Moved Permanently <br><small>Status Code: 301</small></label>
</div>
</div>
</div>
<button class="ui teal button" onclick="addRules();"><i class="ui plus icon"></i> Add Redirection Rule</button>
<div class="ui green message" id="ruleAddSucc" style="display:none;">
<i class="ui green checkmark icon"></i> Redirection Rules Added
</div>
</div>
<script>
$(".checkbox").checkbox();
function resetForm() {
document.getElementById("rurl").value = "";
document.getElementsByName("destination-url")[0].value = "";
document.getElementsByName("forward-childpath")[0].checked = true;
}
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 redirectType = document.querySelector('input[name="redirect-type"]:checked').value;
$.ajax({
url: "/api/redirect/add",
method: "POST",
data: {
redirectUrl: redirectUrl,
destUrl: destUrl,
forwardChildpath: forwardChildpath,
redirectType: parseInt(redirectType),
},
success: function(data){
if (data.error != undefined){
alert(data.error);
}else{
$("#ruleAddSucc").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
}
initRedirectionRuleList();
}
});
}
function deleteRule(obj){
let targetURL = $(obj).attr("rurl");
targetURL = JSON.parse(decodeURIComponent(targetURL));
if (confirm("Confirm remove redirection from " + targetURL + " ?")){
$.ajax({
url: "/api/redirect/delete",
method: "POST",
data: {
redirectUrl: targetURL,
},
success: function(data){
if (data.error != undefined){
alert(data.error);
}else{
$("#delRuleSucc").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
}
initRedirectionRuleList();
}
});
}
}
function initRedirectionRuleList(){
$("#redirectionRuleList").html("");
$.get("/api/redirect/list", function(data){
data.forEach(function(entry){
$("#redirectionRuleList").append(`<tr>
<td>${entry.RedirectURL} </td>
<td style="text-align: center;"><i class="blue arrow right icon"></i></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.StatusCode==307?"Temporary Redirect (307)":"Moved Permanently (301)"}</td>
<td><button onclick="deleteRule(this);" rurl="${encodeURIComponent(JSON.stringify(entry.RedirectURL))}" title="Delete redirection rule" class="ui mini red icon basic button"><i class="trash icon"></i></button></td>
</tr>`);
});
if (data.length == 0){
$("#redirectionRuleList").append(`<tr colspan="4"><td><i class="checkmark icon"></i> No redirection rule</td></tr>`);
}
});
}
initRedirectionRuleList();
$("#rurl").on('change', (event) => {
const value = event.target.value.trim().replace(/^(https?:\/\/)/, '');
event.target.value = value;
});
</script>

View File

@ -1,61 +0,0 @@
<h3><i class="ui home icon"></i> Set Proxy Root</h3>
<p>For all routing not found in the proxy rule, will be redirected to the proxy root server.</p>
<div class="ui form">
<div class="field">
<label>Proxy Root</label>
<input type="text" id="proxyRoot">
<small>E.g. localhost:8080</small>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="rootReqTLS">
<label>Root require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
</div>
</div>
</div>
<br>
<button class="ui teal button" onclick="setProxyRoot()"><i class="home icon" ></i> Set Proxy Root</button>
<div class="ui green message" id="ProxyRootUpdate" style="display:none">
<i class="ui checkmark icon"></i> Proxy Root Updated
</div>
<script>
function initRootInfo(){
$.get("/api/proxy/list?type=root", function(data){
if (data == null){
}else{
$("#proxyRoot").val(data.Domain);
}
});
}
initRootInfo();
function setProxyRoot(){
var newpr = $("#proxyRoot").val();
if (newpr.trim() == ""){
$("#proxyRoot").parent().addClass('error');
return
}else{
$("#proxyRoot").parent().removeClass('error');
}
var rootReqTls = $("#rootReqTLS")[0].checked;
//Create the endpoint by calling add
$.ajax({
url: "/api/proxy/add",
data: {"type": "root", tls: rootReqTls, ep: newpr},
success: function(data){
if (data.error != undefined){
alert(data.error);
}else{
//OK
initRootInfo();
$("#ProxyRootUpdate").stop().slideDown('fast').delay(3000).slideUp('fast');
}
}
});
}
</script>

View File

@ -1,100 +0,0 @@
<h3><i class="ui exchange icon"></i> New Proxy Endpoint</h3>
<p>You can create a proxy endpoing by subdomain or virtual directories</p>
<div class="ui form">
<div class="field">
<label>Proxy Type</label>
<div class="ui selection dropdown">
<input type="hidden" id="ptype" value="subd">
<i class="dropdown icon"></i>
<div class="default text">Proxy Type</div>
<div class="menu">
<div class="item" data-value="subd">Sub-domain</div>
<div class="item" data-value="vdir">Virtual Directory</div>
</div>
</div>
</div>
<div class="field">
<label>Subdomain Matching Keyword / Virtual Directory Name</label>
<input type="text" id="rootname" placeholder="s1.mydomain.com">
<div class="ui message">
Example of subdomain matching keyword:<br>
<code>s1.arozos.com</code> <br>(Any access starting with s1.arozos.com will be proxy to the IP address below)<br>
Example of virtual directory name: <br>
<code>/s1/home</code> <br>(Any access to {this_server}/s1/ will be proxy to the IP address below)
</div>
</div>
<div class="field">
<label>IP Address or Domain Name with port</label>
<input type="text" id="proxyDomain">
<small>E.g. 192.168.0.101:8000 or example.com</small>
</div>
<div class="field">
<div class="ui checkbox">
<input type="checkbox" id="reqTls">
<label>Proxy Target require TLS Connection <br><small>(i.e. Your proxy target starts with https://)</small></label>
</div>
</div>
<button class="ui teal button" onclick="newProxyEndpoint();">Create Proxy Endpoint</button>
</div>
<div class="ui green message" id="proxyaddSucc" style="display:none">
<i class="ui checkmark icon"></i> Proxy Endpoint Added
</div>
<script>
//New Proxy Endpoint
function newProxyEndpoint(){
var type = $("#ptype").val();
var rootname = $("#rootname").val();
var proxyDomain = $("#proxyDomain").val();
var useTLS = $("#reqTls")[0].checked;
if (rootname.trim() == ""){
$("#rootname").parent().addClass("error");
return
}else{
$("#rootname").parent().removeClass("error");
}
if (proxyDomain.trim() == ""){
$("#proxyDomain").parent().addClass("error");
return
}else{
$("#proxyDomain").parent().removeClass("error");
}
//Create the endpoint by calling add
$.ajax({
url: "/api/proxy/add",
data: {type: type, rootname: rootname, tls: useTLS, ep: proxyDomain},
success: function(data){
if (data.error != undefined){
alert(data.error);
}else{
//OK
listVdirs();
listSubd();
$("#proxyaddSucc").stop().slideDown('fast').delay(3000).slideUp('fast');
//Clear old data
$("#rootname").val("");
$("#proxyDomain").val("");
}
}
});
}
//Generic functions for delete rp endpoints
function deleteEndpoint(ptype, epoint){
if (confirm("Confirm remove proxy for :" + epoint + " (type: " + ptype + ")?")){
$.ajax({
url: "/api/proxy/del",
data: {ep: epoint, ptype: ptype},
success: function(){
listVdirs();
listSubd();
}
})
}
}
</script>

View File

@ -1,369 +0,0 @@
<div class="ui stackable four column grid">
<div class="column">
<div id="serverstatus" class="ui green statustab inverted segment">
<h4 class="ui header">
<i class="power off icon"></i>
<div class="content">
<span id="statusTitle">Offline</span>
<div style="color: white;" class="sub header" id="statusText">Reverse proxy server is offline</div>
</div>
</h4>
</div>
</div>
<div class="column">
<div id="connections" class="ui statustab summary segment">
<h4 class="ui header">
<i class="exchange icon"></i>
<div class="content">
<span id="summaryTotalCount"></span> <small>Req. Today</small>
<div class="sub header" style="margin-top: 0.4em;">
<i class="green circle check icon"></i> <span id="summarySuccCount"></span>
/ <i class="red red exclamation circle icon"></i> <span id="summaryErrCount"></span>
</div>
</div>
</h4>
</div>
</div>
<div class="column">
<div id="connections" class="ui statustab segment" style="background-color: #f0ece1; border: 0px solid transparent;">
<h4 class="ui header">
<i class="arrows alternate horizontal icon"></i>
<div class="content">
<span id="forwardtype"></span>
<div class="sub header" id="forwardtypeList">
</div>
</div>
</h4>
</div>
</div>
<div class="column">
<div id="connections" class="ui statustab inverted segment" style="background-color: #7d8e88;">
<h4 class="ui header">
<i class="map marker alternate icon"></i>
<div class="content">
<span id="country"></span>
<div class="sub header" id="countryList">
</div>
</div>
</h4>
</div>
</div>
</div>
<div class="ui divider"></div>
<p>Inbound Port (Port to be proxied)</p>
<div class="ui action fluid input">
<input type="text" id="incomingPort" placeholder="Incoming Port" value="80">
<button class="ui button" onclick="handlePortChange();">Apply</button>
</div>
<br>
<div id="tls" class="ui toggle checkbox">
<input type="checkbox">
<label>Use TLS to serve proxy request</label>
</div>
<br>
<div id="redirect" class="ui toggle checkbox" style="margin-top: 0.6em;">
<input type="checkbox">
<label>Force redirect HTTP request to HTTPS<br>
<small>(Only apply when listening port is 443)</small></label>
</div>
<br>
<div id="portUpdateSucc" class="ui green message" style="display:none;">
<i class="ui green checkmark icon"></i> Setting Updated
</div>
<Br>
<button id="startbtn" class="ui teal button" onclick="startService();">Start Service</button>
<button id="stopbtn" class="ui red disabled button" onclick="stopService();">Stop Service</button>
<div id="rploopbackWarning" class="ui segment" style="display:none;">
<b><i class="yellow warning icon"></i> Loopback Routing Warning</b><br>
<small>This management interface is a loopback proxied service. <br>If you want to shutdown the reverse proxy server, please remove the proxy rule for the management interface and refresh.</small>
</div>
<div id="statusErrmsg" class="ui red message" style="display: none;"></div>
<div class="ui divider"></div>
<div class="ui two column stackable grid">
<div class="column">
<p>Visitor Counts</p>
<table class="ui unstackable celled table">
<thead>
<tr>
<th>Country ISO Code</th>
<th>Unique Visitors</th>
</tr>
</thead>
<tbody id="countryCodetable">
<tr>
<td colspan="2">No Data</td>
</tr>
</tbody>
</table>
</div>
<div class="column">
<p>Proxy Request Types</p>
<table class="ui unstackable celled table">
<thead>
<tr>
<th>Proxy Type</th>
<th>Count</th>
</tr>
</thead>
<tbody id="forwardTypeTable">
<tr>
<td colspan="2">No Data</td>
</tr>
</tbody>
</table>
</div>
</div>
<br>
<button class="ui basic green button" onclick="getDailySummaryDetails();"><i class="refresh icon"></i> Refresh</button>
<script>
let loopbackProxiedInterface = false;
//Initial the start stop button if this is reverse proxied
$.get("/api/proxy/requestIsProxied", function(data){
if (data == true){
//This management interface is reverse proxied by itself
//do not allow turning off the proxy
$("#stopbtn").addClass("disabled");
loopbackProxiedInterface = true;
$("#rploopbackWarning").show();
}
});
//Get the latest server status from proxy server
function initRPStaste(){
$.get("/api/proxy/status", function(data){
if (data.Running == true){
$("#startbtn").addClass("disabled");
if (!loopbackProxiedInterface){
$("#stopbtn").removeClass("disabled");
}
$("#serverstatus").addClass("green");
$("#statusTitle").text("Online");
$("#statusText").text("Serving request on port: " + data.Option.Port);
}else{
$("#startbtn").removeClass("disabled");
$("#stopbtn").addClass("disabled");
$("#statusTitle").text("Offline");
$("#statusText").text("Reverse proxy server is offline");
$("#serverstatus").removeClass("green");
}
$("#incomingPort").val(data.Option.Port);
});
}
function abbreviateNumber(value) {
var newValue = value;
var suffixes = ["", "k", "m", "b", "t"];
var suffixNum = 0;
while (newValue >= 1000 && suffixNum < suffixes.length - 1) {
newValue /= 1000;
suffixNum++;
}
if (value > 1000){
newValue = newValue.toFixed(2);
}
return newValue + suffixes[suffixNum];
}
function getDailySummaryDetails(){
function sortObjectByValue(obj) {
// Convert object to array of [key, value] pairs
const entries = Object.entries(obj);
// Sort array based on value of each pair
entries.sort((a, b) => {
return b[1] - a[1];
});
// Convert sorted array back to object
const sortedObj = {};
for (const [key, value] of entries) {
sortedObj[key] = value;
}
return sortedObj;
}
$.get("/api/stats/countries", function(data){
data = sortObjectByValue(data);
$("#country").html((Object.keys(data)[0])?Object.keys(data)[0]:"No Data");
$("#countryList").html(`
<div style="color: white;">
${(Object.keys(data)[1])?Object.keys(data)[1]:"No Data"}<br>
${(Object.keys(data)[2])?Object.keys(data)[2]:"No Data"}
</div>
`);
//populate the table
$("#countryCodetable").html("");
for (const [key, value] of Object.entries(data)) {
$("#countryCodetable").append(`<tr>
<td>${key}</td>
<td>${value}</td>
</tr>`);
}
if (Object.keys(data).length == 0){
$("#countryCodetable").append(`<tr>
<td colspan="2">No Data</td>
</tr>`);
}
});
//Filter forward type
function fft(ft){
if (ft.indexOf("-") >= 0){
ft = ft.replace("-", " (");
ft = ft + ")";
}
ft = ft.charAt(0).toUpperCase() + ft.slice(1);
return ft;
}
$.get("/api/stats/summary", function(data){
data = sortObjectByValue(data.ForwardTypes);
$("#forwardtype").html((Object.keys(data)[0])?fft(Object.keys(data)[0]) + ": " + abbreviateNumber(data[Object.keys(data)[0]]):"No Data");
$("#forwardtypeList").html(`
<div>
${(Object.keys(data)[1])?fft(Object.keys(data)[1]) + ": " + abbreviateNumber(data[Object.keys(data)[1]]):"No Data"}<br>
${(Object.keys(data)[2])?fft(Object.keys(data)[2]) + ": " + abbreviateNumber(data[Object.keys(data)[2]]):"No Data"}
</div>
`);
$("#forwardTypeTable").html("");
for (const [key, value] of Object.entries(data)) {
$("#forwardTypeTable").append(`<tr>
<td>${key}</td>
<td>${value}</td>
</tr>`);
}
if (Object.keys(data).length == 0){
$("#forwardTypeTable").append(`<tr>
<td colspan="2">No Data</td>
</tr>`);
}
});
}
getDailySummaryDetails();
function getDailySummary(){
$.get("/api/stats/summary?fast=true", function(data){
console.log(data);
$("#summaryTotalCount").text(abbreviateNumber(data.TotalRequest));
$("#summarySuccCount").text(abbreviateNumber(data.ValidRequest));
$("#summaryErrCount").text(abbreviateNumber(data.ErrorRequest));
});
}
setInterval(function(){
getDailySummary();
}, 10000);
getDailySummary();
//Start and stop service button
function startService(){
$.post("/api/proxy/enable", {enable: true}, function(data){
if (data.error != undefined){
statusErrmsg(data.error);
}
initRPStaste();
});
}
function stopService(){
$.post("/api/proxy/enable", {enable: false}, function(data){
if (data.error != undefined){
statusErrmsg(data.error);
}
initRPStaste();
});
}
//Show error message
function statusErrmsg(message){
$("#statusErrmsg").html(`<i class="red remove icon"></i> ${message}`);
$("#statusErrmsg").slideDown('fast').delay(5000).slideUp('fast');
}
function handlePortChange(){
var newPortValue = $("#incomingPort").val();
if (isNaN(newPortValue - 1)){
alert("Invalid incoming port value");
return;
}
$.post("/api/proxy/setIncoming", {incoming: newPortValue}, function(data){
if (data.error != undefined){
statusErrmsg(data.error);
}
$("#portUpdateSucc").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
initRPStaste();
});
}
function initHTTPtoHTTPSRedirectSetting(){
$.get("/api/proxy/useHttpsRedirect", function(data){
if (data == true){
$("#redirect").checkbox("set checked");
}
//Initiate the input listener on the checkbox
$("#redirect").find("input").on("change", function(){
let thisValue = $("#redirect").checkbox("is checked");
$.ajax({
url: "/api/proxy/useHttpsRedirect",
data: {set: thisValue},
success: function(data){
if (data.error != undefined){
alert(data.error);
}else{
//Updated
$("#portUpdateSucc").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
initRPStaste();
}
}
})
});
});
}
initHTTPtoHTTPSRedirectSetting();
function initTlsSetting(){
$.get("/api/cert/tls", function(data){
if (data == true){
$("#tls").checkbox("set checked");
}
//Initiate the input listener on the checkbox
$("#tls").find("input").on("change", function(){
let thisValue = $("#tls").checkbox("is checked");
$.ajax({
url: "/api/cert/tls",
data: {set: thisValue},
success: function(data){
if (data.error != undefined){
alert(data.error);
}else{
//Updated
$("#portUpdateSucc").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
initRPStaste();
}
}
})
});
})
}
initTlsSetting();
</script>

View File

@ -1,42 +0,0 @@
<table class="ui celled sortable unstackable compact table">
<thead>
<tr>
<th>Matching Domain</th>
<th>Proxy To</th>
<th class="no-sort">Remove</th>
</tr>
</thead>
<tbody id="subdList">
</tbody>
</table>
<button class="ui icon green basic button" onclick="listSubd();"><i class="refresh icon"></i> Refresh</button>
<script>
listSubd();
function listSubd(){
$("#subdList").html(``);
$.get("/api/proxy/list?type=subd", function(data){
if (data.error !== undefined){
$("#subdList").append(`<tr>
<td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td>
</tr>`);
}else if (data.length == 0){
$("#subdList").append(`<tr>
<td data-label="" colspan="3"><i class="checkmark icon"></i> No Subdomain Proxy Record</td>
</tr>`);
}else{
data.forEach(subd => {
let tlsIcon = "";
if (subd.RequireTLS){
tlsIcon = `<i class="lock icon"></i>`;
}
$("#subdList").append(`<tr>
<td data-label="">${subd.MatchingDomain}</td>
<td data-label="">${subd.Domain} ${tlsIcon}</td>
<td data-label=""><button class="ui circular mini red basic button" onclick='deleteEndpoint("subd","${subd.MatchingDomain}")'><i class="remove icon"></i> Delete</button></td>
</tr>`);
});
}
});
}
</script>

View File

@ -1,71 +0,0 @@
<h3><i class="blue exchange icon"></i> Port Forward</h3>
<p>Port forward using UPnP protocol</p>
<div class="ui divider"></div>
<div class="ui message">
<h4><i class="ui loading spinner icon"></i> Checking Upnp State</h4>
<p><i class="ui info circle icon"></i> If you are hosting this server under a home router which you have no access to, you can try port forward your services port and expose them to the internet via Upnp protocol.
Note that not all router support this function, sometime this might be disabled by your ISP or administrator.</p>
<button style="position: absolute; right: 0.6em; top: 0.6em;" class="ui circular basic icon button"><i class="ui green refresh icon"></i></button>
</div>
<div class="ui form">
<div class="field">
<div class="ui toggle checkbox">
<input type="checkbox" name="upnp">
<label>Enable UPnP</label>
</div>
</div>
<div class="field">
<label>Port to Forward</label>
<div class="ui input">
<input type="number" min="1" max="65535" name="forwardPort" placeholder="Forwardable Port">
</div>
</div>
<div class="field">
<label>Rule Name</label>
<div class="ui input">
<input type="text" name="ruleName" placeholder="Rule Name">
</div>
</div>
<div class="field">
<button class="ui teal button" type="button" name="addRule"><i class="ui add icon"></i> Add Rule</button>
</div>
</div>
<div class="ui basic segment">
<p>Forwarded Ports</p>
<table class="ui basic table">
<thead>
<tr>
<th>Port Forwarded</th>
<th>Name of Rule</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>80</td>
<td>HTTP</td>
<td><button class="ui button negative" type="button" name="deleteRule">Delete</button></td>
</tr>
<tr>
<td>22</td>
<td>SSH</td>
<td><button class="ui button negative" type="button" name="deleteRule">Delete</button></td>
</tr>
</tbody>
</table>
</div>
<script>
//Get value of the form
function getFormValues() {
var formValues = {};
formValues.upnp = $('input[name="upnp"]').prop('checked');
formValues.forwardPort = $('input[name="forwardPort"]').val();
formValues.ruleName = $('input[name="ruleName"]').val();
return formValues;
}
</script>

View File

@ -1,165 +0,0 @@
<h3><i class="clock green icon"></i> Uptime Monitor</h3>
<p>Check the online state of proxied targets</p>
<div class="ui toggle checkbox" id="utmEnable">
<input type="checkbox" name="utmEnable">
<label>Enable External Access</label>
</div>
<div class="ui message">
You can expose the uptime monitor interface to public by adding: <br>
<code>%uptime_monitor%</code>
<br>as a subdomain or virtual directory target URL in the "Create Proxy Rules" tab.
</div>
<div class="ui divider"></div>
<div id="utmrender" class="ui basic segment">
<div class="ui basic segment">
<h4 class="ui header">
<i class="red remove icon"></i>
<div class="content">
Uptime Monitoring service is currently unavailable
<div class="sub header">This might cause by an error in cluster communication within the host servers. Please wait for administrator to resolve the issue.</div>
</div>
</h4>
</div>
</div>
<div align="center">
<button class="ui basic circular green icon button" onclick="reloadUptimeList();"><i class="refresh icon"></i></button>
</div>
<script>
$('#utmEnable').checkbox({
onChange: function() {
var utmEnable = $('input[name="utmEnable"]').is(":checked");
$.post({
url: '/api/toggle-utm',
data: {utmEnable: utmEnable},
success: function(response) {
console.log(response);
},
error: function(error) {
console.log(error);
}
});
}
});
function initUptimeTable(){
$.get("/api/utm/list", function(data){
let records = data;
renderRecords(records);
})
}
initUptimeTable();
function reloadUptimeList(){
$("#utmrender").html(`<div class="ui segment">
<div class="ui active inverted dimmer" style="z-index: 2;">
<div class="ui text loader">Loading</div>
</div>
<br><br><br><br>
</div>`);
setTimeout(initUptimeTable, 300);
}
//For every 5 minutes
setInterval(function(){
$.get("/api/utm/list", function(data){
console.log("Status Updated");
records = data;
renderRecords(records);
});
}, (300 * 1000));
function renderRecords(records){
$("#utmrender").html("");
for (let [key, value] of Object.entries(records)) {
renderUptimeData(key, value);
}
}
function format_time(s) {
const date = new Date(s * 1e3);
return(date.toLocaleString());
}
function renderUptimeData(key, value){
if (value.length == 0){
return
}
let id = value[0].ID;
let name = value[0].Name;
let url = value[0].URL;
let protocol = value[0].Protocol;
//Generate the status dot
let statusDotList = ``;
for(var i = 0; i < (288 - value.length); i++){
//Padding
statusDotList += `<div class="padding statusDot"></div>`
}
let ontimeRate = 0;
for (var i = 0; i < value.length; i++){
//Render status to html
let thisStatus = value[i];
let dotType = "";
if (thisStatus.Online){
if (thisStatus.StatusCode < 200 || thisStatus.StatusCode >= 300){
dotType = "error";
}else{
dotType = "online";
}
ontimeRate++;
}else{
dotType = "offline";
}
let datetime = format_time(thisStatus.Timestamp);
statusDotList += `<div title="${datetime}" class="${dotType} statusDot"></div>`
}
ontimeRate = ontimeRate / value.length * 100;
let ontimeColor = "#df484a"
if (ontimeRate > 0.8){
ontimeColor = "#3bd671";
}else if(ontimeRate > 0.5) {
ontimeColor = "#f29030";
}
//Check of online status now
let currentOnlineStatus = "Unknown";
let onlineStatusCss = ``;
if (value[value.length - 1].Online){
currentOnlineStatus = `<i class="circle icon"></i> Online`;
onlineStatusCss = `color: #3bd671;`;
}else{
currentOnlineStatus = `<i class="circle icon"></i> Offline`;
onlineStatusCss = `color: #df484a;`;
}
//Generate the html
$("#utmrender").append(`<div class="ui basic segment statusbar">
<div class="domain">
<div style="position: absolute; top: 0; right: 0.4em;">
<p class="onlineStatus" style="display: inline-block; font-size: 1.3em; padding-right: 0.5em; padding-left: 0.3em; ${onlineStatusCss}">${currentOnlineStatus}</p>
</div>
<div>
<h3 class="ui header" style="margin-bottom: 0.2em;">${name}</h3>
<a href="${url}" target="_blank">${url}</a> | <span style="color: ${ontimeColor};">${(ontimeRate).toFixed(2)}%<span>
</div>
<div class="ui basic label protocol" style="position: absolute; bottom: 0; right: 0.2em; margin-bottom: -0.6em;">
proto: ${protocol}
</div>
</div>
<div class="status" style="marign-top: 1em;">
${statusDotList}
</div>
<div class="ui divider"></div>
</div>`);
}
</script>

View File

@ -1,207 +0,0 @@
<h3><i class="paperclip icon"></i> Utilities</h3>
<p>You might find these tools helpful when setting up your gateway server</p>
<div class="ui divider"></div>
<div class="selfauthOnly">
<h3><i class="ui user icon"></i> Account Management</h3>
<p>Functions to help management the current account</p>
<div class="ui basic segment">
<h5><i class="chevron down icon"></i> Change Password</h5>
<div class="ui form">
<div class="field">
<label>Current Password</label>
<input type="password" name="oldPassword" placeholder="Current Password">
</div>
<div class="field">
<label>New Password</label>
<input type="password" name="newPassword" placeholder="New Password">
</div>
<div class="field">
<label>Confirm New Password</label>
<input type="password" name="confirmNewPassword" placeholder="Confirm New Password">
</div>
<button class="ui teal button" onclick="changePassword()"><i class="ui key icon"></i> Change Password</button>
</div>
<div id="passwordChangeSuccMsg" class="ui green message" style="display:none;">
<i class="ui circle checkmark green icon "></i> Password Updated
</div>
</div>
<div class="ui divider"></div>
</div>
<h3><i class="ui code icon"></i> IP Address Converter</h3>
<p>No experience with CIDR notations? Here are some tools you can use to make setting up easier.</p>
<div class="ui basic segment">
<h5><i class="chevron down icon"></i> IP Range to CIDR Conversion</h5>
<div class="ui message">
<i class="info circle icon"></i> Note that the CIDR generated here covers additional IP address before or after the given range. If you need more details settings, please use CIDR with a smaller range and add additional IPs for detail range adjustment.
</div>
<div class="ui input">
<input type="text" placeholder="Start IP" id="startIpInput">
</div>
<div class="ui input">
<input type="text" placeholder="End IP" id="endIpInput">
</div>
<br>
<button style="margin-top: 0.6em;" class="ui button" onclick="convertToCIDR()">Convert</button>
<p>Results: <div id="cidrOutput">N/A</div></p>
</div>
<div class="ui basic segment">
<h5><i class="chevron down icon"></i> CIDR to IP Range Conversion</h5>
<div class="ui action input">
<input type="text" placeholder="CIDR" id="cidrInput">
<button class="ui button" onclick="convertToIPRange()">Convert</button>
</div>
<p>Results: <div id="ipRangeOutput">N/A</div></p>
</div>
<script>
/*
Account Password utilities
*/
$.get("/api/auth/userCount", function(data){
if (data == 0){
//Using external auth manager. Hide options
$(".selfauthOnly").hide();
}
})
function changePassword() {
const oldPassword = document.getElementsByName('oldPassword')[0].value;
const newPassword = document.getElementsByName('newPassword')[0].value;
const confirmNewPassword = document.getElementsByName('confirmNewPassword')[0].value;
$.ajax({
type: "POST",
url: "/api/auth/changePassword",
data: {
oldPassword: oldPassword,
newPassword: newPassword,
confirmPassword: confirmNewPassword,
},
success: function (data) {
if (data.error != undefined){
alert(data.error);
}else{
$("#passwordChangeSuccMsg").stop().finish().slideDown("fast").delay(3000).slideUp("fast");
$('[name="oldPassword"]').val('');
$('[name="newPassword"]').val('');
$('[name="confirmNewPassword"]').val('');
}
},
error: function (xhr, status, error) {
alert("Error changing password: " + error);
},
});
}
/*
IP Address Utilities
*/
//events handler
function convertToCIDR() {
const startIp = document.getElementById('startIpInput').value.trim();
const endIp = document.getElementById('endIpInput').value.trim();
const cidrOutput = document.getElementById('cidrOutput');
const cidr = ipRangeToCIDR(startIp, endIp);
const ipRange = cidrToRange(cidr);
cidrOutput.innerHTML = `CIDR: ${cidr} <br> (Cover range: ${ipRange[0]} to ${ipRange[1]})`;
}
// CIDR to IP Range Conversion
function convertToIPRange() {
const cidr = document.getElementById('cidrInput').value.trim();
const ipRangeOutput = document.getElementById('ipRangeOutput');
const ipRange = cidrToRange(cidr);
ipRangeOutput.innerHTML = `Start IP: ${ipRange[0]}<br>End IP: ${ipRange[1]}`;
}
//Ip conversion function
function cidrToRange(cidr) {
var range = [2];
cidr = cidr.split('/');
var cidr_1 = parseInt(cidr[1])
range[0] = long2ip((ip2long(cidr[0])) & ((-1 << (32 - cidr_1))));
start = ip2long(range[0])
range[1] = long2ip( start + Math.pow(2, (32 - cidr_1)) - 1);
return range;
}
function ipRangeToCIDR(ipStart, ipEnd) {
var start = ip2long(ipStart);
var end = ip2long(ipEnd);
var cidr = 32;
while (start != end) {
start >>= 1;
end >>= 1;
cidr--;
}
return ipStart + '/' + cidr;
}
function ip2long (argIP) {
// discuss at: https://locutus.io/php/ip2long/
// original by: Waldo Malqui Silva (https://waldo.malqui.info)
// improved by: Victor
// revised by: fearphage (https://my.opera.com/fearphage/)
// revised by: Theriault (https://github.com/Theriault)
// estarget: es2015
// example 1: ip2long('192.0.34.166')
// returns 1: 3221234342
// example 2: ip2long('0.0xABCDEF')
// returns 2: 11259375
// example 3: ip2long('255.255.255.256')
// returns 3: false
let i = 0
// PHP allows decimal, octal, and hexadecimal IP components.
// PHP allows between 1 (e.g. 127) to 4 (e.g 127.0.0.1) components.
const pattern = new RegExp([
'^([1-9]\\d*|0[0-7]*|0x[\\da-f]+)',
'(?:\\.([1-9]\\d*|0[0-7]*|0x[\\da-f]+))?',
'(?:\\.([1-9]\\d*|0[0-7]*|0x[\\da-f]+))?',
'(?:\\.([1-9]\\d*|0[0-7]*|0x[\\da-f]+))?$'
].join(''), 'i')
argIP = argIP.match(pattern) // Verify argIP format.
if (!argIP) {
// Invalid format.
return false
}
// Reuse argIP variable for component counter.
argIP[0] = 0
for (i = 1; i < 5; i += 1) {
argIP[0] += !!((argIP[i] || '').length)
argIP[i] = parseInt(argIP[i]) || 0
}
// Continue to use argIP for overflow values.
// PHP does not allow any component to overflow.
argIP.push(256, 256, 256, 256)
// Recalculate overflow of last component supplied to make up for missing components.
argIP[4 + argIP[0]] *= Math.pow(256, 4 - argIP[0])
if (argIP[1] >= argIP[5] ||
argIP[2] >= argIP[6] ||
argIP[3] >= argIP[7] ||
argIP[4] >= argIP[8]) {
return false
}
return argIP[1] * (argIP[0] === 1 || 16777216) +
argIP[2] * (argIP[0] <= 2 || 65536) +
argIP[3] * (argIP[0] <= 3 || 256) +
argIP[4] * 1
}
function long2ip (ip) {
// discuss at: https://locutus.io/php/long2ip/
// original by: Waldo Malqui Silva (https://fayr.us/waldo/)
// example 1: long2ip( 3221234342 )
// returns 1: '192.0.34.166'
if (!isFinite(ip)) {
return false
}
return [ip >>> 24 & 0xFF, ip >>> 16 & 0xFF, ip >>> 8 & 0xFF, ip & 0xFF].join('.')
}
</script>

View File

@ -1,48 +0,0 @@
<table class="ui celled sortable unstackable compact table">
<thead>
<tr>
<th>Virtual Directory</th>
<th>Proxy To</th>
<th class="no-sort">Remove</th>
</tr>
</thead>
<tbody id="vdirList">
<tr>
<td data-label="">test</td>
<td data-label="">test</td>
<td data-label=""><button class="ui circular mini red basic button"><i class="remove icon"></i> Remove Proxy</button></td>
</tr>
</tbody>
</table>
<button class="ui icon green basic button" onclick="listVdirs();"><i class="refresh icon"></i> Refresh</button>
<script>
//Virtual directories functions
listVdirs();
function listVdirs(){
$("#vdirList").html(``);
$.get("/api/proxy/list?type=vdir", function(data){
if (data.error !== undefined){
$("#vdirList").append(`<tr>
<td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td>
</tr>`);
}else if (data.length == 0){
$("#vdirList").append(`<tr>
<td data-label="" colspan="3"><i class="checkmark icon"></i> No Virtual Directory Record</td>
</tr>`);
}else{
data.forEach(vdir => {
let tlsIcon = "";
if (vdir.RequireTLS){
tlsIcon = `<i title="TLS mode" class="lock icon"></i>`;
}
$("#vdirList").append(`<tr>
<td data-label="">${vdir.Root}</td>
<td data-label="">${vdir.Domain} ${tlsIcon}</td>
<td data-label=""><button class="ui circular mini red basic button" onclick='deleteEndpoint("vdir","${vdir.Root}")'><i class="remove icon"></i> Delete</button></td>
</tr>`);
});
}
});
}
</script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -1,55 +0,0 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.js
"></script>
<title>Forbidden</title>
<style>
#msg{
position: absolute;
top: calc(50% - 150px);
left: calc(50% - 250px);
width: 500px;
height: 300px;
text-align: center;
}
#footer{
position: fixed;
padding: 2em;
padding-left: 5em;
padding-right: 5em;
bottom: 0px;
left: 0px;
width: 100%;
}
small{
word-break: break-word;
}
</style>
</head>
<body>
<div id="msg">
<h1 style="font-size: 6em; margin-bottom: 0px;"><i class="red ban icon"></i></h1>
<div>
<h3 style="margin-top: 1em;">403 - Forbidden</h3>
<div class="ui divider"></div>
<p>You do not have permission to view this directory or page. <br>
This might be caused by the site admin has blacklisted your country or IP address</p>
<div class="ui divider"></div>
<div style="text-align: left;">
<small>Request time: <span id="reqtime"></span></small><br>
<small id="reqURLDisplay">Request URI: <span id="requrl"></span></small>
</div>
</div>
</div>
<script>
$("#reqtime").text(new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}));
$("#requrl").text(window.location.href);
</script>
</body>
</html>

View File

@ -1,157 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
<meta charset="UTF-8">
<meta name="theme-color" content="#4b75ff">
<link rel="icon" type="image/png" href="img/small_icon.png"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
<title>404 - Host Not Found</title>
<style>
h1, h2, h3, h4, h5, p, a, span{
font-family: 'Noto Sans TC', sans-serif;
font-weight: 300;
color: rgb(88, 88, 88)
}
.diagram{
background-color: #ebebeb;
box-shadow:
inset 0px 11px 8px -10px #CCC,
inset 0px -11px 8px -10px #CCC;
padding-bottom: 2em;
}
.diagramHeader{
margin-top: 0.2em;
}
@media (max-width:512px) {
.widescreenOnly{
display: none !important;
}
.four.wide.column:not(.widescreenOnly){
width: 50% !important;
}
.ui.grid{
justify-content: center !important;
}
}
</style>
</head>
<body>
<div>
<br><br>
<div class="ui container">
<h1 style="font-size: 4rem;">Error 404</h1>
<p style="font-size: 2rem; margin-bottom: 0.4em;">Target Host Not Found</p>
<small id="timestamp"></small>
</div>
<br><br>
</div>
<div class="diagram">
<div class="ui text container">
<div class="ui grid">
<div class="four wide column widescreenOnly" align="center">
<svg version="1.1" id="client_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100%" height="" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
82.989,143.204 "/>
</svg>
<small>You</small>
<h2 class="diagramHeader">Browser</h2>
<p style="font-weight: 500; color: #9bca3e;">Working</p>
</div>
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
</div>
<div class="four wide column widescreenOnly" align="center">
<svg version="1.1" id="cloud_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100%" height="" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
83.971,148.123 "/>
</svg>
<small>Gateway Node</small>
<h2 class="diagramHeader">Reverse Proxy</h2>
<p style="font-weight: 500; color: #9bca3e;">Working</p>
</div>
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
</div>
<div class="four wide column" align="center">
<svg version="1.1" id="host_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100%" height="" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
</svg>
<small id="host"></small>
<h2 class="diagramHeader">Host</h2>
<p style="font-weight: 500; color: #bd2426;">Not Found</p>
</div>
</div>
</div>
</div>
<div>
<br>
<div class="ui container">
<div class="ui stackable grid">
<div class="eight wide column">
<h1>What happend?</h1>
<p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p>
</div>
<div class="eight wide column">
<h1>What can I do?</h1>
<h5 style="font-weight: 500;">If you are a visitor of this website: </h5>
<p>Please try again in a few minutes</p>
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
<div class="ui bulleted list">
<div class="item">Check if the target web server is online</div>
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
</div>
</div>
</div>
</div>
<br>
</div>
<div class="ui divider"></div>
<div class="ui container" style="color: grey; font-size: 90%">
<p>Powered by Zoraxy</p>
</div>
<br><br>
<script>
$("#timestamp").text(new Date());
$("#host").text(location.href);
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
82.989,143.204 "/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
83.971,148.123 "/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="600px" height="200px" viewBox="0 0 600 200" enable-background="new 0 0 600 200" xml:space="preserve">
<g>
<path fill="#213040" d="M138.761,47.403l-16.064,17.87c9.504,8.549,15.48,20.94,15.48,34.728c0,13.785-5.976,26.179-15.48,34.726
l16.063,17.871c14.393-12.945,23.445-31.717,23.445-52.597C162.206,79.115,153.155,60.351,138.761,47.403z"/>
<path fill="#213040" d="M44.198,152.596l16.064-17.869c-9.503-8.547-15.48-20.941-15.48-34.726c0-13.79,5.978-26.179,15.48-34.728
l-16.063-17.87C29.807,60.351,20.753,79.115,20.753,100C20.753,120.881,29.807,139.652,44.198,152.596z"/>
</g>
<polygon fill="#A9D1F3" points="106.581,38.326 91.48,56.48 76.38,38.326 "/>
<polygon fill="#A9D1F3" points="106.581,143.52 91.48,161.674 76.379,143.52 "/>
<circle fill="#A9D1F3" cx="91.48" cy="100" r="22.422"/>
<g>
<path d="M194.194,132.898l43.232-66.846h-39.238V54.539h56.155v8.224l-43.233,66.729h43.703v11.629h-60.619V132.898z"/>
<path d="M263.038,108.814c0-21.499,14.45-33.951,30.544-33.951c15.977,0,30.31,12.452,30.31,33.951
c0,21.498-14.333,33.951-30.31,33.951C277.488,142.766,263.038,130.313,263.038,108.814z M310.029,108.814
c0-13.627-6.344-22.791-16.447-22.791c-10.221,0-16.564,9.164-16.564,22.791c0,13.744,6.344,22.674,16.564,22.674
C303.686,131.488,310.029,122.559,310.029,108.814z"/>
<path d="M339.869,76.391h11.042l1.176,11.629h0.234c4.582-8.223,11.396-13.156,18.444-13.156c3.173,0,5.169,0.471,7.166,1.293
l-2.35,11.863c-2.349-0.704-3.877-1.057-6.578-1.057c-5.287,0-11.632,3.643-15.626,13.981v40.177h-13.509V76.391z"/>
<path d="M380.868,123.969c0-13.98,11.748-21.146,38.649-24.082c-0.115-7.402-2.819-13.98-12.334-13.98
c-6.813,0-13.158,3.056-18.68,6.578l-5.052-9.162c6.696-4.23,15.742-8.459,26.08-8.459c16.096,0,23.497,10.104,23.497,27.374
v38.884h-11.044l-1.058-7.4h-0.469c-5.875,5.051-12.806,9.045-20.56,9.045C388.739,142.766,380.868,135.365,380.868,123.969z
M419.518,124.322V108.58c-19.147,2.23-25.61,7.166-25.61,14.332c0,6.461,4.348,9.047,10.104,9.047
C409.649,131.959,414.231,129.256,419.518,124.322z"/>
<path d="M464.63,107.405l-19.383-31.015h14.686l7.636,13.039c1.996,3.643,3.995,7.285,6.109,10.927h0.587
c1.645-3.642,3.406-7.284,5.287-10.927l6.813-13.039h14.099l-19.386,32.424l20.795,32.307h-14.685l-8.459-13.744
c-2.115-3.76-4.346-7.754-6.697-11.396h-0.586c-1.997,3.643-3.995,7.52-5.992,11.396l-7.518,13.744h-14.098L464.63,107.405z"/>
<path d="M508.096,166.85l2.586-10.574c1.176,0.354,3.054,0.939,4.815,0.939c6.932,0,11.045-5.168,13.394-12.1l1.41-4.463
l-25.611-64.262h13.746l11.865,33.363c1.996,5.758,3.993,12.1,5.991,18.209h0.587c1.645-5.992,3.406-12.334,5.053-18.209
l10.456-33.363h13.038l-23.73,68.607c-5.051,13.863-11.865,23.143-25.375,23.143C512.914,168.141,510.329,167.672,508.096,166.85z"
/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<path fill="#213040" d="M366.358,143.646l-37.493,41.708c22.181,19.953,36.132,48.874,36.132,81.057s-13.952,61.104-36.133,81.057
l37.493,41.708c33.594-30.219,54.725-74.022,54.725-122.765C421.082,217.667,399.952,173.865,366.358,143.646z"/>
<path fill="#213040" d="M145.64,389.175l37.494-41.707c-22.181-19.953-36.133-48.874-36.133-81.058s13.951-61.104,36.133-81.058
l-37.493-41.707c-33.594,30.219-54.725,74.021-54.725,122.765C90.916,315.152,112.047,358.956,145.64,389.175z"/>
</g>
<polygon fill="#A9D1F3" points="291.247,122.458 256,164.833 220.753,122.458 "/>
<polygon fill="#A9D1F3" points="291.247,367.987 256,410.362 220.752,367.987 "/>
<circle fill="#A9D1F3" cx="256" cy="266.41" r="52.333"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="600px" height="200px" viewBox="0 0 600 200" enable-background="new 0 0 600 200" xml:space="preserve">
<g>
<path fill="#213040" d="M464.798,122.75c0-4.639,3.26-7.434,7.405-7.434c2.018,0,3.614,0.854,4.611,1.805l-1.374,1.437
c-0.865-0.737-1.885-1.281-3.192-1.281c-2.816,0-4.789,2.058-4.789,5.415c0,3.378,1.84,5.474,4.723,5.474
c1.485,0,2.615-0.602,3.591-1.533l1.397,1.437c-1.33,1.339-2.971,2.076-5.1,2.076C467.969,130.145,464.798,127.447,464.798,122.75z
"/>
<path fill="#213040" d="M480.032,115.568h2.572v12.383h6.917v1.922h-9.489V115.568z"/>
<path fill="#213040" d="M491.542,123.739v-8.171h2.572v8.308c0,3.241,1.463,4.289,3.503,4.289c2.062,0,3.569-1.048,3.569-4.289
v-8.308h2.483v8.171c0,4.658-2.438,6.405-6.053,6.405S491.542,128.397,491.542,123.739z"/>
<path fill="#213040" d="M506.489,128.029l1.507-1.553c1.176,1.009,2.771,1.688,4.368,1.688c1.951,0,3.037-0.815,3.037-2.019
c0-1.301-1.086-1.708-2.594-2.29l-2.262-0.854c-1.596-0.582-3.325-1.649-3.325-3.746c0-2.251,2.239-3.939,5.365-3.939
c1.929,0,3.725,0.698,4.922,1.805l-1.308,1.437c-1.021-0.796-2.173-1.281-3.614-1.281c-1.663,0-2.727,0.719-2.727,1.824
c0,1.242,1.308,1.689,2.615,2.174l2.24,0.835c1.95,0.718,3.325,1.767,3.325,3.862c0,2.29-2.173,4.173-5.742,4.173
C510.059,130.145,507.975,129.349,506.489,128.029z"/>
<path fill="#213040" d="M523.94,117.471h-4.767v-1.902h12.172v1.902h-4.789v12.402h-2.616V117.471z"/>
<path fill="#213040" d="M534.187,115.568h9.645v1.902h-7.072v3.979h5.986v1.902h-5.986v4.6h7.316v1.922h-9.889V115.568z"/>
<path fill="#213040" d="M552.724,124.108h-2.66v5.765h-2.572v-14.305h5.344c3.37,0,5.897,1.048,5.897,4.153
c0,2.213-1.353,3.533-3.415,4.096l3.991,6.056h-2.904L552.724,124.108z M552.524,122.304c2.35,0,3.658-0.854,3.658-2.582
c0-1.727-1.309-2.329-3.658-2.329h-2.461v4.911H552.524z"/>
<path fill="#213040" d="M464.798,146.035c0-4.639,3.304-7.434,7.649-7.434c2.306,0,3.857,0.893,4.833,1.805l-1.374,1.437
c-0.82-0.699-1.818-1.281-3.393-1.281c-3.037,0-5.055,2.058-5.055,5.415c0,3.378,1.796,5.474,5.188,5.474
c0.998,0,1.974-0.271,2.527-0.699v-3.338h-3.215v-1.863h5.565v6.191c-1.086,0.951-2.927,1.688-5.144,1.688
C468.013,153.43,464.798,150.732,464.798,146.035z"/>
<path fill="#213040" d="M484.379,138.854h2.971l5.409,14.305h-2.727l-1.375-4.057h-5.676l-1.374,4.057h-2.639L484.379,138.854z
M488.037,147.316l-0.644-1.922c-0.532-1.553-1.02-3.145-1.529-4.755h-0.089c-0.488,1.63-0.998,3.202-1.53,4.755l-0.643,1.922
H488.037z"/>
<path fill="#213040" d="M496.708,140.756h-4.767v-1.902h12.172v1.902h-4.789v12.402h-2.616V140.756z"/>
<path fill="#213040" d="M506.954,138.854h9.645v1.902h-7.072v3.979h5.986v1.902h-5.986v4.6h7.316v1.922h-9.889V138.854z"/>
<path fill="#213040" d="M518.619,138.854h2.639l1.529,7.434c0.289,1.533,0.577,3.048,0.887,4.601h0.089
c0.333-1.553,0.731-3.086,1.108-4.601l2.085-7.434h2.283l2.106,7.434c0.377,1.515,0.731,3.048,1.108,4.601h0.11
c0.289-1.553,0.555-3.086,0.82-4.601l1.553-7.434h2.461l-3.215,14.305h-3.171l-2.194-7.938c-0.267-1.126-0.532-2.193-0.754-3.28
h-0.089c-0.244,1.087-0.51,2.154-0.776,3.28l-2.15,7.938h-3.126L518.619,138.854z"/>
<path fill="#213040" d="M543.345,138.854h2.971l5.409,14.305h-2.727l-1.375-4.057h-5.676l-1.374,4.057h-2.639L543.345,138.854z
M547.003,147.316l-0.644-1.922c-0.532-1.553-1.02-3.145-1.529-4.755h-0.089c-0.488,1.63-0.998,3.202-1.53,4.755l-0.643,1.922
H547.003z"/>
<path fill="#213040" d="M556.184,147.763l-4.899-8.909h2.749l1.885,3.805c0.51,1.067,0.976,2.076,1.529,3.144h0.089
c0.532-1.067,1.064-2.076,1.552-3.144l1.907-3.805h2.683l-4.922,8.909v5.396h-2.572V147.763z"/>
</g>
<g>
<path fill="#213040" d="M87.513,84.572l-9.7,10.791c5.739,5.162,9.348,12.645,9.348,20.971c0,8.324-3.609,15.809-9.348,20.971
l9.7,10.791c8.691-7.818,14.158-19.152,14.158-31.762C101.671,103.723,96.205,92.391,87.513,84.572z"/>
<path fill="#213040" d="M30.41,148.094l9.701-10.789c-5.739-5.162-9.348-12.646-9.348-20.971c0-8.327,3.609-15.809,9.348-20.971
l-9.7-10.791c-8.691,7.818-14.158,19.15-14.158,31.762C16.252,128.943,21.719,140.277,30.41,148.094z"/>
</g>
<polygon fill="#A9D1F3" points="68.081,79.09 58.962,90.053 49.843,79.09 "/>
<polygon fill="#A9D1F3" points="68.081,142.613 58.962,153.576 49.843,142.613 "/>
<circle fill="#A9D1F3" cx="58.962" cy="116.333" r="13.54"/>
<g>
<path d="M120.229,141.305l35.696-55.193h-32.398v-9.506h46.366v6.79l-35.696,55.097h36.084v9.603h-50.052V141.305z"/>
<path d="M177.072,121.42c0-17.752,11.931-28.033,25.22-28.033c13.192,0,25.026,10.281,25.026,28.033
c0,17.751-11.834,28.033-25.026,28.033C189.002,149.453,177.072,139.171,177.072,121.42z M215.872,121.42
c0-11.252-5.238-18.818-13.58-18.818c-8.439,0-13.677,7.566-13.677,18.818c0,11.349,5.238,18.721,13.677,18.721
C210.634,140.141,215.872,132.769,215.872,121.42z"/>
<path d="M240.509,94.647h9.117l0.971,9.603h0.193c3.783-6.79,9.409-10.863,15.229-10.863c2.619,0,4.268,0.388,5.917,1.067
l-1.94,9.796c-1.939-0.582-3.201-0.873-5.432-0.873c-4.365,0-9.604,3.008-12.901,11.544v33.174h-11.154V94.647z"/>
<path d="M274.361,133.933c0-11.543,9.7-17.46,31.913-19.885c-0.096-6.111-2.328-11.543-10.184-11.543
c-5.626,0-10.864,2.522-15.424,5.432l-4.171-7.566c5.529-3.492,12.998-6.984,21.534-6.984c13.29,0,19.401,8.342,19.401,22.602
v32.106h-9.119l-0.873-6.11h-0.387c-4.852,4.17-10.574,7.469-16.976,7.469C280.86,149.453,274.361,143.342,274.361,133.933z
M306.273,134.224v-12.998c-15.81,1.843-21.146,5.917-21.146,11.834c0,5.335,3.59,7.47,8.343,7.47
C298.125,140.529,301.908,138.298,306.273,134.224z"/>
<path d="M343.521,120.256l-16.004-25.608h12.125l6.305,10.767c1.648,3.008,3.299,6.015,5.045,9.021h0.484
c1.357-3.007,2.813-6.014,4.365-9.021l5.625-10.767h11.641l-16.006,26.772l17.17,26.675h-12.125l-6.984-11.349
c-1.746-3.104-3.588-6.402-5.529-9.409h-0.484c-1.648,3.007-3.299,6.208-4.947,9.409l-6.207,11.349h-11.641L343.521,120.256z"/>
<path d="M379.41,169.338l2.135-8.73c0.971,0.291,2.521,0.775,3.977,0.775c5.723,0,9.119-4.268,11.059-9.99l1.164-3.686
l-21.146-53.06h11.35l9.797,27.548c1.648,4.754,3.297,9.991,4.947,15.035h0.484c1.357-4.947,2.813-10.185,4.172-15.035
l8.633-27.548h10.766l-19.594,56.648c-4.17,11.446-9.797,19.108-20.951,19.108C383.389,170.404,381.254,170.018,379.41,169.338z"/>
</g>
<line fill="none" stroke="#595757" stroke-width="2" stroke-miterlimit="10" x1="450.003" y1="75" x2="450.003" y2="164.666"/>
</svg>

Before

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

View File

@ -1,238 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
<meta charset="UTF-8">
<meta name="theme-color" content="#4b75ff">
<link rel="icon" type="image/png" href="./favicon.png" />
<title>Control Panel | Zoraxy</title>
<link rel="stylesheet" href="script/semantic/semantic.min.css">
<script src="script/jquery-3.6.0.min.js"></script>
<script src="../script/ao_module.js"></script>
<script src="script/semantic/semantic.min.js"></script>
<script src="script/tablesort.js"></script>
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="menubar">
<div class="item">
<img class="logo" src="img/logo.svg">
</div>
<div class="ui right floated buttons menutoggle" style="padding-top: 2px;">
<button class="ui basic icon button" onclick="$('.toolbar').fadeToggle('fast');"><i class="content icon"></i></button>
</div>
<div class="ui right floated buttons" style="padding-top: 2px;">
<button class="ui basic icon button" onclick="logout();"><i class="sign-out icon"></i></button>
</div>
</div>
<div class="wrapper">
<div class="toolbar">
<div id="mainmenu" class="ui secondary vertical pointing menu">
<a class="item active" tag="status">
<i class="blue info circle icon"></i>Status
</a>
<a class="item" tag="vdir">
<i class="yellow folder icon"></i> Virtual Directory
</a>
<a class="item" tag="subd">
<i class="teal sitemap icon"></i> Subdomain Proxy
</a>
<a class="item" tag="rules">
<i class="brown plus square icon"></i> Create Proxy Rules
</a>
<a class="item" tag="setroot">
<i class="home icon"></i> Set Proxy Root
</a>
<div class="ui divider menudivider">Access & Connections</div>
<a class="item" tag="cert">
<i class="orange lock icon"></i> TLS / SSL certificate
</a>
<a class="item" tag="redirectset">
<i class="violet level up alternate icon"></i> Redirection
</a>
<a class="item" tag="blacklist">
<i class="red ban icon"></i> Blacklist
</a>
<a class="item" tag="upnp">
<i class="blue exchange icon"></i> Port Forward
</a>
<div class="ui divider menudivider">Bridging</div>
<a class="item" tag="">
<i class="remove icon"></i> TCP Proxy
</a>
<a class="item" tag="">
<i class="remove icon"></i> HTTP over Websocket
</a>
<div class="ui divider menudivider">Others</div>
<a class="item" tag="utm">
<i class="green time icon"></i> Uptime Monitor
</a>
<a class="item" tag="">
<i class="remove icon"></i> Network Tools
</a>
<a class="item" tag="utils">
<i class="grey paperclip icon"></i> Utilities
</a>
<!-- Add more components here -->
</div>
</div>
<div class="contentWindow">
<!-- Status Tab -->
<div id="status" class="functiontab" target="status.html" style="display: block ;">
<br><br><div class="ui active centered inline loader"></div>
</div>
<!-- Virtual Directory Tab -->
<div id="vdir" class="functiontab" target="vdir.html"></div>
<!-- Subdomain Proxy -->
<div id="subd" class="functiontab" target="subd.html"></div>
<!-- Create Rules -->
<div id="rules" class="functiontab" target="rules.html"></div>
<!-- Set proxy root -->
<div id="setroot" class="functiontab" target="rproot.html"></div>
<!-- Set TLS cert -->
<div id="cert" class="functiontab" target="cert.html"></div>
<!-- Redirections -->
<div id="redirectset" class="functiontab" target="redirection.html"></div>
<!-- Blacklist -->
<div id="blacklist" class="functiontab" target="blacklist.html"></div>
<!-- UPnP based port fowarding -->
<div id="upnp" class="functiontab" target="upnp.html"></div>
<!-- Up Time Monitor -->
<div id="utm" class="functiontab" target="uptime.html"></div>
<!-- Utilities -->
<div id="utils" class="functiontab" target="utils.html"></div>
</div>
</div>
</div>
<br><br>
<div class="ui divider"></div>
<div class="ui container" style="color: grey; font-size: 90%">
<p>CopyRight Zoraxy project and its author, 2022 - <span class="year"></span></p>
</div>
<br><br>
<script>
$(".year").text(new Date().getFullYear());
/*
Loader function
Load all the components view from the
components/ directory into their corrisponding divs
*/
let loadingComponents = 0;
function initTabs(callback=undefined){
$('.functiontab').each(function(){
let loadTarget = $(this).attr("target");
if (loadTarget != undefined){
$(this).load("./components/" + loadTarget, function(){
loadingComponents--;
});
loadingComponents++;
}else{
$(this).html(`<p>Unable to load components for this tab</p>`);
}
})
if (callback != undefined){
waitInit(callback);
}
}
function waitInit(callback = undefined, retryCount = 0){
if (loadingComponents > 0 && retryCount < 5){
setTimeout(function(){
waitInit(callback, retryCount++);
}, 300);
}else if (loadingComponents == 0){
callback();
}else{
alert("Missing component. Please check if your installation is complete.")
}
}
initTabs(function(){
initRPStaste();
if (window.location.hash.length > 1){
let tabID = window.location.hash.substr(1);
openTabById(tabID);
}else{
openTabById("status");
}
$(".ui.dropdown").dropdown();
$(".ui.checkbox").checkbox();
//Click on the current tab
$("#mainmenu").find(".item").each(function(){
$(this).on("click", function(event){
let tabid = $(this).attr("tag");
openTabById(tabid);
});
});
//Initialize all table that is sortable
$('table').tablesort();
});
function logout() {
$.get("/api/auth/logout", function(response) {
if (response === "OK") {
window.location.href = "/";
}
});
}
function getTabButtonById(targetTabId){
let targetTabBtn = undefined;
$("#mainmenu").find(".item").each(function(){
let tabid = $(this).attr("tag");
if (tabid == targetTabId){
targetTabBtn = $(this);
}
});
return targetTabBtn;
}
//Select and open a tab by its tag id
function openTabById(tabID){
let targetBtn = getTabButtonById(tabID);
if (targetBtn == undefined){
alert("Invalid tabid given");
return;
}
if (window.innerWidth < 750){
//RWD mode, hide toolbar
$(".toolbar").fadeOut('fast');
}
$("#mainmenu").find(".item").removeClass("active");
$(targetBtn).addClass("active");
$(".functiontab").hide();
$("#" + tabID).fadeIn('fast');
$('html,body').animate({scrollTop: 0}, 'fast');
window.location.hash = tabID;
}
$(window).on("resize", function(){
if (window.innerWidth >= 750 && $(".toolbar").is(":visible") == false){
$(".toolbar").show();
}
});
</script>
</body>
</html>

View File

@ -1,262 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="./favicon.png" />
<title>Login | Zoraxy</title>
<link rel="stylesheet" href="script/semantic/semantic.min.css">
<script type="application/javascript" src="script/jquery-3.6.0.min.js"></script>
<script type="application/javascript" src="script/semantic/semantic.min.js"></script>
<style>
body {
background: rgb(245,245,245);
background: linear-gradient(28deg, rgba(245,245,245,1) 63%, rgba(255,255,255,1) 100%);
}
.background{
position: fixed;
top: 0;
right: 0;
height: 100%;
width: 100%;
opacity: 0.8;
z-index: -99;
background-image: url("img/public/bg.png");
background-size: auto 100%;
background-position: right top;
background-repeat: no-repeat;
overflow-x: hidden;
}
form {
margin:auto;
}
#loginForm{
height: 100%;
background-color: white;
width: 25em;
margin-left: 10em;
margin-top: 0 !important;
margin-bottom: 0 !important;
}
@media all and (max-width: 550px) {
/* CSS rules here for screens lower than 750px */
#loginForm{
width: calc(100% - 4em);
margin-left: 2em;
}
}
#errmsg{
color: #9f3a38;
margin-top: 1em;
margin-bottom: 0.4em;
text-align: left;
}
.registerOnly{
display:none;
}
.ui.fluid.button.registerOnly{
display:none;
}
</style>
</head>
<body>
<div class="background"></div>
<div id="loginForm" class="ui middle aligned center aligned grid">
<div class="column">
<form class="ui large form">
<div class="ui basic segment">
<img class="ui fluid image" src="img/public/logo.svg" style="pointer-events:none;">
<p class="registerOnly">Account Setup</p>
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input id="username" type="text" name="username" placeholder="Username">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input id="magic" type="password" name="password" placeholder="Password">
</div>
</div>
<div class="field registerOnly">
<div class="ui left icon input">
<i class="lock icon"></i>
<input id="repeatMagic" type="password" name="passwordconfirm" placeholder="Confirm Password">
</div>
</div>
<div class="field loginOnly" style="text-align: left;">
<div class="ui checkbox">
<input id="rmbme" type="checkbox" tabindex="0" class="hidden">
<label>Remember Me</label>
</div>
</div>
<div id="loginbtn" class="ui fluid basic blue button loginOnly">Login</div>
<div id="regsiterbtn" class="ui fluid basic blue button registerOnly">Create</div>
<div id="errmsg"></div>
</div>
<div class="ui divider"></div>
<small>Proudly powered by Zoraxy</small>
</form>
</div>
</div>
<script>
var redirectionAddress = "/";
var loginAddress = "/api/auth/login";
$(".checkbox").checkbox();
$(document).ready(function(){
var currentdate = new Date();
var datetime = currentdate.getDate() + "/"
+ (currentdate.getMonth()+1) + "/"
+ currentdate.getFullYear() + " "
+ currentdate.getHours() + ":"
+ currentdate.getMinutes() + ":"
+ currentdate.getSeconds();
$("#requestTime").text(datetime);
//Check if this is a new system
$.get("/api/auth/userCount", function(data){
if (data == 0){
//Allow user creation
$(".loginOnly").hide();
$(".registerOnly").show();
}
});
//Check if the user already logged in
$.get("/api/auth/checkLogin",function(data){
try{
if (data === true || data.trim() == "true"){
//User already logged in. Redirect to target page.
if (redirectionAddress == ""){
//Redirect back to index
window.location.href = "/";
}else{
console.log(data);
//window.location.href = redirectionAddress;
}
}
}catch(ex){
//Assume not logged in
console.log(data);
}
});
});
function updateYear() {
const year = new Date().getFullYear();
const elements = document.getElementsByClassName("year");
for (let i = 0; i < elements.length; i++) {
elements[i].textContent = year;
}
}
updateYear();
//Event handlers for buttons
$("#loginbtn").on("click",function(){
login();
});
$("input").on("keydown",function(event){
if (event.keyCode === 13) {
event.preventDefault();
if ($(this).attr("id") == "magic"){
login();
}else{
//Fuocus to password field
$("#magic").focus();
}
}
});
$("#regsiterbtn").on("click", function(event){
var username = $("#username").val();
var magic = $("#magic").val();
var repeatMagic = $("#repeatMagic").val();
if (magic !== repeatMagic) {
alert("Password does not match");
return;
}
$.ajax({
url: "/api/auth/register",
method: "POST",
data: {
username: username,
password: magic
},
success: function(data) {
if (data.error != undefined){
alert(data.error);
}else{
//Register success. Refresh page
window.location.reload();
}
},
error: function(xhr, status, error) {
console.error("Error registering user:", error);
}
});
});
//Login system with the given username and password
function login(){
var username = $("#username").val();
var magic = $("#magic").val();
var rmbme = document.getElementById("rmbme").checked;
$("#errmsg").stop().finish().slideUp("fast");
$("input").addClass('disabled');
$.post(loginAddress, {"username": username, "password": magic, "rmbme": rmbme}).done(function(data){
if (data.error !== undefined){
//Something went wrong during the login
$("#errmsg").html(`<i class="red remove icon"></i> ${data.error}`);
$("#errmsg").stop().finish().slideDown('fast');
}else if(data.redirect !== undefined){
//LDAP Related Code
window.location.href = data.redirect;
}else{
//Login succeed
if (redirectionAddress == ""){
//Redirect back to index
window.location.href = "./";
}else{
window.location.href = redirectionAddress;
}
}
$("input").removeClass('disabled');
});
}
function get(name){
if(name=(new RegExp('[?&]'+encodeURIComponent(name)+'=([^&]*)')).exec(location.search))
return decodeURIComponent(name[1]);
}
$(".thisyear").text(new Date().getFullYear());
function updateRenderElements(){
if (window.innerHeight < 520){
$(".bottombar").hide();
}else{
$(".bottombar").show();
}
}
updateRenderElements();
$(window).on("resize", function(){
updateRenderElements();
});
</script>
</body>
</html>

View File

@ -1,170 +0,0 @@
/*
index.html style overwrite
*/
body{
background-color:white;
}
.functiontab{
display:none;
}
.menubar{
width: 100%;
padding: 0.4em;
padding-left: 1.2em;
padding-right: 1.2em;
background-color: #f5f5f5;
margin-bottom: 1em;
position: fixed;
top: 0;
width: 100%;
z-index: 10;
box-shadow: 0px 1px 5px 0px rgba(38,38,38,0.26);
}
.menubar .logo{
height: 36px;
}
.menubar .item{
display: inline-block;
vertical-align: middle;
}
.wrapper{
display: flex;
flex-wrap: wrap;
margin-top: 4.6em;
}
.toolbar{
width: 240px;
min-width: 240px;
}
.contentWindow{
padding: 1em;
flex: 1;
}
.menutoggle{
display: none !important;
}
@media screen and (max-width: 750px) {
.toolbar {
position: fixed;
display: inline-block;
width: 50%;
background-color: white;
top: 3.6em;
right: 0;
height: 100%;
margin-bottom: 1em;
z-index: 9;
padding: 1em;
display:none;
border-left: 1px solid rgb(206, 206, 206);
}
.menutoggle{
display: inline-block !important;
}
#mainmenu{
width: calc(100% - 1em);
}
.contentWindow{
display: inline-block;
width: 100%;
}
}
.menudivider{
font-size: 0.8em !important;
color: #9c9c9c !important;
padding-left: 0.6em;
}
/*
Global rules overwrite
*/
.ui.teal.button{
background-color: #7fbbc5 !important;
}
.ui.red.button:not(.basic){
background-color: #c78e70 !important;
}
/*
Status style overwrite
*/
#serverstatus.green{
background-color: #7fbbc5 !important;
}
#serverstatus:not(.green){
background-color: #5f5e5c !important;
}
.statustab{
min-height: 5.5em;
}
#summaryTotalCount{
font-size: 1.6em;
font-weight: 300;
}
.statustab.summary{
background-color: #f1f1f1 !important;
border: 0px solid transparent !important;
}
.statustab.summary span, .statustab.summary i{
color: rgb(37, 37, 37);
}
/*
Uptime Monitor
*/
#utm{
background-color: white;
border-radius: 1em;
}
.domain{
margin-bottom: 1em;
position: relative;
}
.statusDot{
height: 1.8em;
border-radius: 0.4em;
width: 0.4em;
background-color: #e8e8e8;
display:inline-block;
cursor: pointer;
margin-left: 0.1em;
}
.online.statusDot{
background-color: #3bd671;
}
.error.statusDot{
background-color: #f29030;
}
.offline.statusDot{
background-color: #df484a;
}
.padding.statusDot{
cursor: auto;
}

View File

@ -1,158 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1"/>
<meta charset="UTF-8">
<meta name="theme-color" content="#4b75ff">
<link rel="icon" type="image/png" href="img/small_icon.png"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.9.2/semantic.min.js"></script>
<title>521 - Web server is down</title>
<style>
h1, h2, h3, h4, h5, p, a, span{
font-family: 'Noto Sans TC', sans-serif;
font-weight: 300;
color: rgb(88, 88, 88)
}
.diagram{
background-color: #ebebeb;
box-shadow:
inset 0px 11px 8px -10px #CCC,
inset 0px -11px 8px -10px #CCC;
padding-bottom: 2em;
}
.diagramHeader{
margin-top: 0.2em;
}
@media (max-width:512px) {
.widescreenOnly{
display: none !important;
}
.four.wide.column:not(.widescreenOnly){
width: 50% !important;
}
.ui.grid{
justify-content: center !important;
}
}
</style>
</head>
<body>
<div>
<br><br>
<div class="ui container">
<h1 style="font-size: 4rem;">Error 521</h1>
<p style="font-size: 2rem; margin-bottom: 0.4em;">Web server is down</p>
<small id="timestamp"></small>
</div>
<br><br>
</div>
<div class="diagram">
<div class="ui text container">
<div class="ui grid">
<div class="four wide column widescreenOnly" align="center">
<svg version="1.1" id="client_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100%" height="" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="#C9CACA" d="M184.795,143.037c0,9.941-8.059,18-18,18H33.494c-9.941,0-18-8.059-18-18V44.952c0-9.941,8.059-18,18-18
h133.301c9.941,0,18,8.059,18,18V143.037z"/>
<circle fill="#FFFFFF" cx="37.39" cy="50.88" r="6.998"/>
<circle fill="#FFFFFF" cx="54.115" cy="50.88" r="6.998"/>
<path fill="#FFFFFF" d="M167.188,50.88c0,3.865-3.133,6.998-6.998,6.998H72.379c-3.865,0-6.998-3.133-6.998-6.998l0,0
c0-3.865,3.133-6.998,6.998-6.998h87.811C164.055,43.882,167.188,47.015,167.188,50.88L167.188,50.88z"/>
<rect x="31.296" y="66.907" fill="#FFFFFF" width="132.279" height="77.878"/>
<circle fill="#9BCA3E" cx="96.754" cy="144.785" r="37.574"/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="108.497,133.047 93.373,153.814
82.989,143.204 "/>
</svg>
<small>You</small>
<h2 class="diagramHeader">Browser</h2>
<p style="font-weight: 500; color: #9bca3e;">Working</p>
</div>
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
</div>
<div class="four wide column widescreenOnly" align="center">
<svg version="1.1" id="cloud_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100%" height="" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<ellipse fill="#9FA0A0" cx="46.979" cy="108.234" rx="25.399" ry="25.139"/>
<circle fill="#9FA0A0" cx="109.407" cy="100.066" r="50.314"/>
<circle fill="#9FA0A0" cx="22.733" cy="129.949" r="19.798"/>
<circle fill="#9FA0A0" cx="172.635" cy="125.337" r="24.785"/>
<path fill="#9FA0A0" d="M193.514,133.318c0,9.28-7.522,16.803-16.803,16.803H28.223c-9.281,0-16.803-7.522-16.803-16.803l0,0
c0-9.28,7.522-16.804,16.803-16.804h148.488C185.991,116.515,193.514,124.038,193.514,133.318L193.514,133.318z"/>
<circle fill="#9BCA3D" cx="100" cy="149.572" r="38.267"/>
<polyline fill="none" stroke="#FFFFFF" stroke-width="8" stroke-miterlimit="10" points="113.408,136.402 95.954,160.369
83.971,148.123 "/>
</svg>
<small>Gateway Node</small>
<h2 class="diagramHeader">Reverse Proxy</h2>
<p style="font-weight: 500; color: #9bca3e;">Working</p>
</div>
<div class="two wide column widescreenOnly" style="margin-top: 8em; text-align: center;">
<i class="ui big grey exchange alternate icon" style="color:rgb(167, 167, 167) !important;"></i>
</div>
<div class="four wide column" align="center">
<svg version="1.1" id="host_svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100%" height="" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="#999999" d="M168.484,113.413c0,9.941,3.317,46.324-6.624,46.324H35.359c-9.941,0-5.873-39.118-5.715-46.324
l17.053-50.909c1.928-9.879,8.059-18,18-18h69.419c9.941,0,15.464,7.746,18,18L168.484,113.413z"/>
<rect x="38.068" y="118.152" fill="#FFFFFF" width="122.573" height="34.312"/>
<circle fill="#BD2426" cx="141.566" cy="135.873" r="8.014"/>
<circle fill="#BD2426" cx="99.354" cy="152.464" r="36.343"/>
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="144.125" x2="107.594" y2="161.946"/>
<line fill="none" stroke="#FFFFFF" stroke-width="6" stroke-miterlimit="10" x1="90.5" y1="161.946" x2="107.594" y2="144.79"/>
</svg>
<small id="host"></small>
<h2 class="diagramHeader">Host</h2>
<p style="font-weight: 500; color: #bd2426;">Error</p>
</div>
</div>
</div>
</div>
<div>
<br>
<div class="ui container">
<div class="ui stackable grid">
<div class="eight wide column">
<h1>What happend?</h1>
<p>The web server reported a bad gateway error.<br>For more information, see the error message on the reverse proxy terminal.</p>
</div>
<div class="eight wide column">
<h1>What can I do?</h1>
<h5 style="font-weight: 500;">If you are a visitor of this website: </h5>
<p>Please try again in a few minutes</p>
<h5 style="font-weight: 500;">If you are the owner of this website:</h5>
<div class="ui bulleted list">
<div class="item">Check if the target web server is online</div>
<div class="item">Visit the Reverse Proxy management interface to correct any setting errors</div>
</div>
</div>
</div>
</div>
<br>
</div>
<div class="ui divider"></div>
<div class="ui container" style="color: grey; font-size: 90%">
<p>Powered by Zoraxy</p>
</div>
<br><br>
<script>
$("#timestamp").text(new Date());
$("#host").text(location.href);
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,253 +0,0 @@
/*!
* # Semantic UI 2.4.2 - Accordion
* http://github.com/semantic-org/semantic-ui/
*
*
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/
/*******************************
Accordion
*******************************/
.ui.accordion,
.ui.accordion .accordion {
max-width: 100%;
}
.ui.accordion .accordion {
margin: 1em 0em 0em;
padding: 0em;
}
/* Title */
.ui.accordion .title,
.ui.accordion .accordion .title {
cursor: pointer;
}
/* Default Styling */
.ui.accordion .title:not(.ui) {
padding: 0.5em 0em;
font-family: 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif;
font-size: 1em;
color: rgba(0, 0, 0, 0.87);
}
/* Content */
.ui.accordion .title ~ .content,
.ui.accordion .accordion .title ~ .content {
display: none;
}
/* Default Styling */
.ui.accordion:not(.styled) .title ~ .content:not(.ui),
.ui.accordion:not(.styled) .accordion .title ~ .content:not(.ui) {
margin: '';
padding: 0.5em 0em 1em;
}
.ui.accordion:not(.styled) .title ~ .content:not(.ui):last-child {
padding-bottom: 0em;
}
/* Arrow */
.ui.accordion .title .dropdown.icon,
.ui.accordion .accordion .title .dropdown.icon {
display: inline-block;
float: none;
opacity: 1;
width: 1.25em;
height: 1em;
margin: 0em 0.25rem 0em 0rem;
padding: 0em;
font-size: 1em;
-webkit-transition: opacity 0.1s ease, -webkit-transform 0.1s ease;
transition: opacity 0.1s ease, -webkit-transform 0.1s ease;
transition: transform 0.1s ease, opacity 0.1s ease;
transition: transform 0.1s ease, opacity 0.1s ease, -webkit-transform 0.1s ease;
vertical-align: baseline;
-webkit-transform: none;
transform: none;
}
/*--------------
Coupling
---------------*/
/* Menu */
.ui.accordion.menu .item .title {
display: block;
padding: 0em;
}
.ui.accordion.menu .item .title > .dropdown.icon {
float: right;
margin: 0.21425em 0em 0em 1em;
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
/* Header */
.ui.accordion .ui.header .dropdown.icon {
font-size: 1em;
margin: 0em 0.25rem 0em 0rem;
}
/*******************************
States
*******************************/
.ui.accordion .active.title .dropdown.icon,
.ui.accordion .accordion .active.title .dropdown.icon {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.ui.accordion.menu .item .active.title > .dropdown.icon {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
/*******************************
Types
*******************************/
/*--------------
Styled
---------------*/
.ui.styled.accordion {
width: 600px;
}
.ui.styled.accordion,
.ui.styled.accordion .accordion {
border-radius: 0.28571429rem;
background: #FFFFFF;
-webkit-box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15);
box-shadow: 0px 1px 2px 0 rgba(34, 36, 38, 0.15), 0px 0px 0px 1px rgba(34, 36, 38, 0.15);
}
.ui.styled.accordion .title,
.ui.styled.accordion .accordion .title {
margin: 0em;
padding: 0.75em 1em;
color: rgba(0, 0, 0, 0.4);
font-weight: bold;
border-top: 1px solid rgba(34, 36, 38, 0.15);
-webkit-transition: background 0.1s ease, color 0.1s ease;
transition: background 0.1s ease, color 0.1s ease;
}
.ui.styled.accordion > .title:first-child,
.ui.styled.accordion .accordion .title:first-child {
border-top: none;
}
/* Content */
.ui.styled.accordion .content,
.ui.styled.accordion .accordion .content {
margin: 0em;
padding: 0.5em 1em 1.5em;
}
.ui.styled.accordion .accordion .content {
padding: 0em;
padding: 0.5em 1em 1.5em;
}
/* Hover */
.ui.styled.accordion .title:hover,
.ui.styled.accordion .active.title,
.ui.styled.accordion .accordion .title:hover,
.ui.styled.accordion .accordion .active.title {
background: transparent;
color: rgba(0, 0, 0, 0.87);
}
.ui.styled.accordion .accordion .title:hover,
.ui.styled.accordion .accordion .active.title {
background: transparent;
color: rgba(0, 0, 0, 0.87);
}
/* Active */
.ui.styled.accordion .active.title {
background: transparent;
color: rgba(0, 0, 0, 0.95);
}
.ui.styled.accordion .accordion .active.title {
background: transparent;
color: rgba(0, 0, 0, 0.95);
}
/*******************************
States
*******************************/
/*--------------
Active
---------------*/
.ui.accordion .active.content,
.ui.accordion .accordion .active.content {
display: block;
}
/*******************************
Variations
*******************************/
/*--------------
Fluid
---------------*/
.ui.fluid.accordion,
.ui.fluid.accordion .accordion {
width: 100%;
}
/*--------------
Inverted
---------------*/
.ui.inverted.accordion .title:not(.ui) {
color: rgba(255, 255, 255, 0.9);
}
/*******************************
Theme Overrides
*******************************/
@font-face {
font-family: 'Accordion';
src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMggjB5AAAAC8AAAAYGNtYXAPfOIKAAABHAAAAExnYXNwAAAAEAAAAWgAAAAIZ2x5Zryj6HgAAAFwAAAAyGhlYWT/0IhHAAACOAAAADZoaGVhApkB5wAAAnAAAAAkaG10eAJuABIAAAKUAAAAGGxvY2EAjABWAAACrAAAAA5tYXhwAAgAFgAAArwAAAAgbmFtZfC1n04AAALcAAABPHBvc3QAAwAAAAAEGAAAACAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDZ//3//wAB/+MPKwADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQASAEkAtwFuABMAADc0PwE2FzYXFh0BFAcGJwYvASY1EgaABQgHBQYGBQcIBYAG2wcGfwcBAQcECf8IBAcBAQd/BgYAAAAAAQAAAEkApQFuABMAADcRNDc2MzIfARYVFA8BBiMiJyY1AAUGBwgFgAYGgAUIBwYFWwEACAUGBoAFCAcFgAYGBQcAAAABAAAAAQAAqWYls18PPPUACwIAAAAAAM/9o+4AAAAAz/2j7gAAAAAAtwFuAAAACAACAAAAAAAAAAEAAAHg/+AAAAIAAAAAAAC3AAEAAAAAAAAAAAAAAAAAAAAGAAAAAAAAAAAAAAAAAQAAAAC3ABIAtwAAAAAAAAAKABQAHgBCAGQAAAABAAAABgAUAAEAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEADAAAAAEAAAAAAAIADgBAAAEAAAAAAAMADAAiAAEAAAAAAAQADABOAAEAAAAAAAUAFgAMAAEAAAAAAAYABgAuAAEAAAAAAAoANABaAAMAAQQJAAEADAAAAAMAAQQJAAIADgBAAAMAAQQJAAMADAAiAAMAAQQJAAQADABOAAMAAQQJAAUAFgAMAAMAAQQJAAYADAA0AAMAAQQJAAoANABaAHIAYQB0AGkAbgBnAFYAZQByAHMAaQBvAG4AIAAxAC4AMAByAGEAdABpAG4AZ3JhdGluZwByAGEAdABpAG4AZwBSAGUAZwB1AGwAYQByAHIAYQB0AGkAbgBnAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('truetype'), url(data:application/font-woff;charset=utf-8;base64,d09GRk9UVE8AAASwAAoAAAAABGgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAA9AAAAS0AAAEtFpovuE9TLzIAAAIkAAAAYAAAAGAIIweQY21hcAAAAoQAAABMAAAATA984gpnYXNwAAAC0AAAAAgAAAAIAAAAEGhlYWQAAALYAAAANgAAADb/0IhHaGhlYQAAAxAAAAAkAAAAJAKZAedobXR4AAADNAAAABgAAAAYAm4AEm1heHAAAANMAAAABgAAAAYABlAAbmFtZQAAA1QAAAE8AAABPPC1n05wb3N0AAAEkAAAACAAAAAgAAMAAAEABAQAAQEBB3JhdGluZwABAgABADr4HAL4GwP4GAQeCgAZU/+Lix4KABlT/4uLDAeLa/iU+HQFHQAAAHkPHQAAAH4RHQAAAAkdAAABJBIABwEBBw0PERQZHnJhdGluZ3JhdGluZ3UwdTF1MjB1RjBEOXVGMERBAAACAYkABAAGAQEEBwoNVp38lA78lA78lA77lA773Z33bxWLkI2Qj44I9xT3FAWOj5CNkIuQi4+JjoePiI2Gi4YIi/uUBYuGiYeHiIiHh4mGi4aLho2Ijwj7FPcUBYeOiY+LkAgO+92L5hWL95QFi5CNkI6Oj4+PjZCLkIuQiY6HCPcU+xQFj4iNhouGi4aJh4eICPsU+xQFiIeGiYaLhouHjYePiI6Jj4uQCA74lBT4lBWLDAoAAAAAAwIAAZAABQAAAUwBZgAAAEcBTAFmAAAA9QAZAIQAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADw2gHg/+D/4AHgACAAAAABAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEADgAAAAKAAgAAgACAAEAIPDa//3//wAAAAAAIPDZ//3//wAB/+MPKwADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAEAADfYOJZfDzz1AAsCAAAAAADP/aPuAAAAAM/9o+4AAAAAALcBbgAAAAgAAgAAAAAAAAABAAAB4P/gAAACAAAAAAAAtwABAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAAAAAAAAEAAAAAtwASALcAAAAAUAAABgAAAAAADgCuAAEAAAAAAAEADAAAAAEAAAAAAAIADgBAAAEAAAAAAAMADAAiAAEAAAAAAAQADABOAAEAAAAAAAUAFgAMAAEAAAAAAAYABgAuAAEAAAAAAAoANABaAAMAAQQJAAEADAAAAAMAAQQJAAIADgBAAAMAAQQJAAMADAAiAAMAAQQJAAQADABOAAMAAQQJAAUAFgAMAAMAAQQJAAYADAA0AAMAAQQJAAoANABaAHIAYQB0AGkAbgBnAFYAZQByAHMAaQBvAG4AIAAxAC4AMAByAGEAdABpAG4AZ3JhdGluZwByAGEAdABpAG4AZwBSAGUAZwB1AGwAYQByAHIAYQB0AGkAbgBnAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('woff');
font-weight: normal;
font-style: normal;
}
/* Dropdown Icon */
.ui.accordion .title .dropdown.icon,
.ui.accordion .accordion .title .dropdown.icon {
font-family: Accordion;
line-height: 1;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
font-weight: normal;
font-style: normal;
text-align: center;
}
.ui.accordion .title .dropdown.icon:before,
.ui.accordion .accordion .title .dropdown.icon:before {
content: '\f0da' /*rtl:'\f0d9'*/;
}
/*******************************
User Overrides
*******************************/

View File

@ -1,613 +0,0 @@
/*!
* # Semantic UI 2.4.2 - Accordion
* http://github.com/semantic-org/semantic-ui/
*
*
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/
;(function ($, window, document, undefined) {
'use strict';
window = (typeof window != 'undefined' && window.Math == Math)
? window
: (typeof self != 'undefined' && self.Math == Math)
? self
: Function('return this')()
;
$.fn.accordion = function(parameters) {
var
$allModules = $(this),
time = new Date().getTime(),
performance = [],
query = arguments[0],
methodInvoked = (typeof query == 'string'),
queryArguments = [].slice.call(arguments, 1),
requestAnimationFrame = window.requestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.msRequestAnimationFrame
|| function(callback) { setTimeout(callback, 0); },
returnedValue
;
$allModules
.each(function() {
var
settings = ( $.isPlainObject(parameters) )
? $.extend(true, {}, $.fn.accordion.settings, parameters)
: $.extend({}, $.fn.accordion.settings),
className = settings.className,
namespace = settings.namespace,
selector = settings.selector,
error = settings.error,
eventNamespace = '.' + namespace,
moduleNamespace = 'module-' + namespace,
moduleSelector = $allModules.selector || '',
$module = $(this),
$title = $module.find(selector.title),
$content = $module.find(selector.content),
element = this,
instance = $module.data(moduleNamespace),
observer,
module
;
module = {
initialize: function() {
module.debug('Initializing', $module);
module.bind.events();
if(settings.observeChanges) {
module.observeChanges();
}
module.instantiate();
},
instantiate: function() {
instance = module;
$module
.data(moduleNamespace, module)
;
},
destroy: function() {
module.debug('Destroying previous instance', $module);
$module
.off(eventNamespace)
.removeData(moduleNamespace)
;
},
refresh: function() {
$title = $module.find(selector.title);
$content = $module.find(selector.content);
},
observeChanges: function() {
if('MutationObserver' in window) {
observer = new MutationObserver(function(mutations) {
module.debug('DOM tree modified, updating selector cache');
module.refresh();
});
observer.observe(element, {
childList : true,
subtree : true
});
module.debug('Setting up mutation observer', observer);
}
},
bind: {
events: function() {
module.debug('Binding delegated events');
$module
.on(settings.on + eventNamespace, selector.trigger, module.event.click)
;
}
},
event: {
click: function() {
module.toggle.call(this);
}
},
toggle: function(query) {
var
$activeTitle = (query !== undefined)
? (typeof query === 'number')
? $title.eq(query)
: $(query).closest(selector.title)
: $(this).closest(selector.title),
$activeContent = $activeTitle.next($content),
isAnimating = $activeContent.hasClass(className.animating),
isActive = $activeContent.hasClass(className.active),
isOpen = (isActive && !isAnimating),
isOpening = (!isActive && isAnimating)
;
module.debug('Toggling visibility of content', $activeTitle);
if(isOpen || isOpening) {
if(settings.collapsible) {
module.close.call($activeTitle);
}
else {
module.debug('Cannot close accordion content collapsing is disabled');
}
}
else {
module.open.call($activeTitle);
}
},
open: function(query) {
var
$activeTitle = (query !== undefined)
? (typeof query === 'number')
? $title.eq(query)
: $(query).closest(selector.title)
: $(this).closest(selector.title),
$activeContent = $activeTitle.next($content),
isAnimating = $activeContent.hasClass(className.animating),
isActive = $activeContent.hasClass(className.active),
isOpen = (isActive || isAnimating)
;
if(isOpen) {
module.debug('Accordion already open, skipping', $activeContent);
return;
}
module.debug('Opening accordion content', $activeTitle);
settings.onOpening.call($activeContent);
settings.onChanging.call($activeContent);
if(settings.exclusive) {
module.closeOthers.call($activeTitle);
}
$activeTitle
.addClass(className.active)
;
$activeContent
.stop(true, true)
.addClass(className.animating)
;
if(settings.animateChildren) {
if($.fn.transition !== undefined && $module.transition('is supported')) {
$activeContent
.children()
.transition({
animation : 'fade in',
queue : false,
useFailSafe : true,
debug : settings.debug,
verbose : settings.verbose,
duration : settings.duration
})
;
}
else {
$activeContent
.children()
.stop(true, true)
.animate({
opacity: 1
}, settings.duration, module.resetOpacity)
;
}
}
$activeContent
.slideDown(settings.duration, settings.easing, function() {
$activeContent
.removeClass(className.animating)
.addClass(className.active)
;
module.reset.display.call(this);
settings.onOpen.call(this);
settings.onChange.call(this);
})
;
},
close: function(query) {
var
$activeTitle = (query !== undefined)
? (typeof query === 'number')
? $title.eq(query)
: $(query).closest(selector.title)
: $(this).closest(selector.title),
$activeContent = $activeTitle.next($content),
isAnimating = $activeContent.hasClass(className.animating),
isActive = $activeContent.hasClass(className.active),
isOpening = (!isActive && isAnimating),
isClosing = (isActive && isAnimating)
;
if((isActive || isOpening) && !isClosing) {
module.debug('Closing accordion content', $activeContent);
settings.onClosing.call($activeContent);
settings.onChanging.call($activeContent);
$activeTitle
.removeClass(className.active)
;
$activeContent
.stop(true, true)
.addClass(className.animating)
;
if(settings.animateChildren) {
if($.fn.transition !== undefined && $module.transition('is supported')) {
$activeContent
.children()
.transition({
animation : 'fade out',
queue : false,
useFailSafe : true,
debug : settings.debug,
verbose : settings.verbose,
duration : settings.duration
})
;
}
else {
$activeContent
.children()
.stop(true, true)
.animate({
opacity: 0
}, settings.duration, module.resetOpacity)
;
}
}
$activeContent
.slideUp(settings.duration, settings.easing, function() {
$activeContent
.removeClass(className.animating)
.removeClass(className.active)
;
module.reset.display.call(this);
settings.onClose.call(this);
settings.onChange.call(this);
})
;
}
},
closeOthers: function(index) {
var
$activeTitle = (index !== undefined)
? $title.eq(index)
: $(this).closest(selector.title),
$parentTitles = $activeTitle.parents(selector.content).prev(selector.title),
$activeAccordion = $activeTitle.closest(selector.accordion),
activeSelector = selector.title + '.' + className.active + ':visible',
activeContent = selector.content + '.' + className.active + ':visible',
$openTitles,
$nestedTitles,
$openContents
;
if(settings.closeNested) {
$openTitles = $activeAccordion.find(activeSelector).not($parentTitles);
$openContents = $openTitles.next($content);
}
else {
$openTitles = $activeAccordion.find(activeSelector).not($parentTitles);
$nestedTitles = $activeAccordion.find(activeContent).find(activeSelector).not($parentTitles);
$openTitles = $openTitles.not($nestedTitles);
$openContents = $openTitles.next($content);
}
if( ($openTitles.length > 0) ) {
module.debug('Exclusive enabled, closing other content', $openTitles);
$openTitles
.removeClass(className.active)
;
$openContents
.removeClass(className.animating)
.stop(true, true)
;
if(settings.animateChildren) {
if($.fn.transition !== undefined && $module.transition('is supported')) {
$openContents
.children()
.transition({
animation : 'fade out',
useFailSafe : true,
debug : settings.debug,
verbose : settings.verbose,
duration : settings.duration
})
;
}
else {
$openContents
.children()
.stop(true, true)
.animate({
opacity: 0
}, settings.duration, module.resetOpacity)
;
}
}
$openContents
.slideUp(settings.duration , settings.easing, function() {
$(this).removeClass(className.active);
module.reset.display.call(this);
})
;
}
},
reset: {
display: function() {
module.verbose('Removing inline display from element', this);
$(this).css('display', '');
if( $(this).attr('style') === '') {
$(this)
.attr('style', '')
.removeAttr('style')
;
}
},
opacity: function() {
module.verbose('Removing inline opacity from element', this);
$(this).css('opacity', '');
if( $(this).attr('style') === '') {
$(this)
.attr('style', '')
.removeAttr('style')
;
}
},
},
setting: function(name, value) {
module.debug('Changing setting', name, value);
if( $.isPlainObject(name) ) {
$.extend(true, settings, name);
}
else if(value !== undefined) {
if($.isPlainObject(settings[name])) {
$.extend(true, settings[name], value);
}
else {
settings[name] = value;
}
}
else {
return settings[name];
}
},
internal: function(name, value) {
module.debug('Changing internal', name, value);
if(value !== undefined) {
if( $.isPlainObject(name) ) {
$.extend(true, module, name);
}
else {
module[name] = value;
}
}
else {
return module[name];
}
},
debug: function() {
if(!settings.silent && settings.debug) {
if(settings.performance) {
module.performance.log(arguments);
}
else {
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
module.debug.apply(console, arguments);
}
}
},
verbose: function() {
if(!settings.silent && settings.verbose && settings.debug) {
if(settings.performance) {
module.performance.log(arguments);
}
else {
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
module.verbose.apply(console, arguments);
}
}
},
error: function() {
if(!settings.silent) {
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
module.error.apply(console, arguments);
}
},
performance: {
log: function(message) {
var
currentTime,
executionTime,
previousTime
;
if(settings.performance) {
currentTime = new Date().getTime();
previousTime = time || currentTime;
executionTime = currentTime - previousTime;
time = currentTime;
performance.push({
'Name' : message[0],
'Arguments' : [].slice.call(message, 1) || '',
'Element' : element,
'Execution Time' : executionTime
});
}
clearTimeout(module.performance.timer);
module.performance.timer = setTimeout(module.performance.display, 500);
},
display: function() {
var
title = settings.name + ':',
totalTime = 0
;
time = false;
clearTimeout(module.performance.timer);
$.each(performance, function(index, data) {
totalTime += data['Execution Time'];
});
title += ' ' + totalTime + 'ms';
if(moduleSelector) {
title += ' \'' + moduleSelector + '\'';
}
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
console.groupCollapsed(title);
if(console.table) {
console.table(performance);
}
else {
$.each(performance, function(index, data) {
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
});
}
console.groupEnd();
}
performance = [];
}
},
invoke: function(query, passedArguments, context) {
var
object = instance,
maxDepth,
found,
response
;
passedArguments = passedArguments || queryArguments;
context = element || context;
if(typeof query == 'string' && object !== undefined) {
query = query.split(/[\. ]/);
maxDepth = query.length - 1;
$.each(query, function(depth, value) {
var camelCaseValue = (depth != maxDepth)
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
: query
;
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
object = object[camelCaseValue];
}
else if( object[camelCaseValue] !== undefined ) {
found = object[camelCaseValue];
return false;
}
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
object = object[value];
}
else if( object[value] !== undefined ) {
found = object[value];
return false;
}
else {
module.error(error.method, query);
return false;
}
});
}
if ( $.isFunction( found ) ) {
response = found.apply(context, passedArguments);
}
else if(found !== undefined) {
response = found;
}
if($.isArray(returnedValue)) {
returnedValue.push(response);
}
else if(returnedValue !== undefined) {
returnedValue = [returnedValue, response];
}
else if(response !== undefined) {
returnedValue = response;
}
return found;
}
};
if(methodInvoked) {
if(instance === undefined) {
module.initialize();
}
module.invoke(query);
}
else {
if(instance !== undefined) {
instance.invoke('destroy');
}
module.initialize();
}
})
;
return (returnedValue !== undefined)
? returnedValue
: this
;
};
$.fn.accordion.settings = {
name : 'Accordion',
namespace : 'accordion',
silent : false,
debug : false,
verbose : false,
performance : true,
on : 'click', // event on title that opens accordion
observeChanges : true, // whether accordion should automatically refresh on DOM insertion
exclusive : true, // whether a single accordion content panel should be open at once
collapsible : true, // whether accordion content can be closed
closeNested : false, // whether nested content should be closed when a panel is closed
animateChildren : true, // whether children opacity should be animated
duration : 350, // duration of animation
easing : 'easeOutQuad', // easing equation for animation
onOpening : function(){}, // callback before open animation
onClosing : function(){}, // callback before closing animation
onChanging : function(){}, // callback before closing or opening animation
onOpen : function(){}, // callback after open animation
onClose : function(){}, // callback after closing animation
onChange : function(){}, // callback after closing or opening animation
error: {
method : 'The method you called is not defined'
},
className : {
active : 'active',
animating : 'animating'
},
selector : {
accordion : '.accordion',
title : '.title',
trigger : '.title',
content : '.content'
}
};
// Adds easing
$.extend( $.easing, {
easeOutQuad: function (x, t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
}
});
})( jQuery, window, document );

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,275 +0,0 @@
/*!
* # Semantic UI 2.4.2 - Ad
* http://github.com/semantic-org/semantic-ui/
*
*
* Copyright 2013 Contributors
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
*/
/*******************************
Advertisement
*******************************/
.ui.ad {
display: block;
overflow: hidden;
margin: 1em 0em;
}
.ui.ad:first-child {
margin: 0em;
}
.ui.ad:last-child {
margin: 0em;
}
.ui.ad iframe {
margin: 0em;
padding: 0em;
border: none;
overflow: hidden;
}
/*--------------
Common
---------------*/
/* Leaderboard */
.ui.leaderboard.ad {
width: 728px;
height: 90px;
}
/* Medium Rectangle */
.ui[class*="medium rectangle"].ad {
width: 300px;
height: 250px;
}
/* Large Rectangle */
.ui[class*="large rectangle"].ad {
width: 336px;
height: 280px;
}
/* Half Page */
.ui[class*="half page"].ad {
width: 300px;
height: 600px;
}
/*--------------
Square
---------------*/
/* Square */
.ui.square.ad {
width: 250px;
height: 250px;
}
/* Small Square */
.ui[class*="small square"].ad {
width: 200px;
height: 200px;
}
/*--------------
Rectangle
---------------*/
/* Small Rectangle */
.ui[class*="small rectangle"].ad {
width: 180px;
height: 150px;
}
/* Vertical Rectangle */
.ui[class*="vertical rectangle"].ad {
width: 240px;
height: 400px;
}
/*--------------
Button
---------------*/
.ui.button.ad {
width: 120px;
height: 90px;
}
.ui[class*="square button"].ad {
width: 125px;
height: 125px;
}
.ui[class*="small button"].ad {
width: 120px;
height: 60px;
}
/*--------------
Skyscrapers
---------------*/
/* Skyscraper */
.ui.skyscraper.ad {
width: 120px;
height: 600px;
}
/* Wide Skyscraper */
.ui[class*="wide skyscraper"].ad {
width: 160px;
}
/*--------------
Banners
---------------*/
/* Banner */
.ui.banner.ad {
width: 468px;
height: 60px;
}
/* Vertical Banner */
.ui[class*="vertical banner"].ad {
width: 120px;
height: 240px;
}
/* Top Banner */
.ui[class*="top banner"].ad {
width: 930px;
height: 180px;
}
/* Half Banner */
.ui[class*="half banner"].ad {
width: 234px;
height: 60px;
}
/*--------------
Boards
---------------*/
/* Leaderboard */
.ui[class*="large leaderboard"].ad {
width: 970px;
height: 90px;
}
/* Billboard */
.ui.billboard.ad {
width: 970px;
height: 250px;
}
/*--------------
Panorama
---------------*/
/* Panorama */
.ui.panorama.ad {
width: 980px;
height: 120px;
}
/*--------------
Netboard
---------------*/
/* Netboard */
.ui.netboard.ad {
width: 580px;
height: 400px;
}
/*--------------
Mobile
---------------*/
/* Large Mobile Banner */
.ui[class*="large mobile banner"].ad {
width: 320px;
height: 100px;
}
/* Mobile Leaderboard */
.ui[class*="mobile leaderboard"].ad {
width: 320px;
height: 50px;
}
/*******************************
Types
*******************************/
/* Mobile Sizes */
.ui.mobile.ad {
display: none;
}
@media only screen and (max-width: 767px) {
.ui.mobile.ad {
display: block;
}
}
/*******************************
Variations
*******************************/
.ui.centered.ad {
margin-left: auto;
margin-right: auto;
}
.ui.test.ad {
position: relative;
background: #545454;
}
.ui.test.ad:after {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
text-align: center;
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
content: 'Ad';
color: #FFFFFF;
font-size: 1em;
font-weight: bold;
}
.ui.mobile.test.ad:after {
font-size: 0.85714286em;
}
.ui.test.ad[data-text]:after {
content: attr(data-text);
}
/*******************************
Theme Overrides
*******************************/
/*******************************
User Variable Overrides
*******************************/

Some files were not shown because too many files have changed in this diff Show More