package main
import (
"bufio"
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/json"
"encoding/pem"
"flag"
"fmt"
"io"
"io/fs"
"log"
"math/big"
"mime"
"net"
"net/http"
"net/http/cookiejar"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"time"
)
const HttpPort = 9000
var exPath string
var serverConfig ServerConfig
var hdcPublicKey string
var hdcPrivateKey *rsa.PrivateKey
func cors(fs http.Handler, version string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.Header.Add("Cross-Origin-Opener-Policy", "same-origin")
r.Header.Add("Cross-Origin-Embedder-Policy", "require-corp")
w.Header().Add("Cross-Origin-Opener-Policy", "same-origin")
w.Header().Add("Cross-Origin-Embedder-Policy", "require-corp")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "x-requested-with, authorization, blade-auth")
w.Header().Set("Access-Control-Allow-Methods", "*")
w.Header().Set("Access-Control-Max-Age", "3600")
w.Header().Set("data-version", version)
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Pragma", "no-cache")
fs.ServeHTTP(w, r)
}
}
type AuthVerifyType int
const (
RSA_3072_SHA512 AuthVerifyType = iota
RSA_ENCRYPT
)
func RsaSign(buf string, signKey *rsa.PrivateKey) (string, error) {
hash := sha512.Sum512([]byte(buf))
signature, err := rsa.SignPSS(rand.Reader, signKey, crypto.SHA512, hash[:], &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthEqualsHash,
})
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(signature), nil
}
func RsaSignAndBase64(buf string, authType AuthVerifyType) (string, error) {
var signResult string
var err error
signResult, err = RsaSign(buf, hdcPrivateKey)
if err != nil {
return "", err
}
return signResult, nil
}
func exist(path string) bool {
_, err := os.Stat(path)
if err != nil {
if os.IsExist(err) {
return true
}
return false
}
return true
}
func genSSL() {
if exist("cert/keyFile.key") || exist("cert/certFile.pem") {
fmt.Println("keyFile.key exists")
return
}
max := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, _ := rand.Int(rand.Reader, max)
subject := pkix.Name{
Organization: []string{"www.smartperf.com"},
OrganizationalUnit: []string{"ITs"},
CommonName: "www.smartperf.com",
}
certificate509 := x509.Certificate{
SerialNumber: serialNumber,
Subject: subject,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
}
chekDir("cert")
pk, _ := rsa.GenerateKey(rand.Reader, 3072)
derBytes, _ := x509.CreateCertificate(rand.Reader, &certificate509, &certificate509, &pk.PublicKey, pk)
certOut, _ := os.Create("cert/certFile.pem")
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
certOut.Close()
keyOut, _ := os.Create("cert/keyFile.key")
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)})
keyOut.Close()
}
func genRsa() {
privateKey, err := rsa.GenerateKey(rand.Reader, 3072)
if err != nil {
fmt.Println("Generate hdc rsa private key failed")
return
}
hdcPrivateKey = privateKey
publicKey := &privateKey.PublicKey
pkixPublicKey, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
fmt.Println(err)
return
}
publicKeyBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: pkixPublicKey,
}
hdcPublicKey = string(pem.EncodeToMemory(publicKeyBlock))
}
func main() {
port := HttpPort
isOpen := 1
flag.IntVar(&port, "p", HttpPort, "The port number used")
flag.IntVar(&isOpen, "o", 1, "Whether to immediately open the website in your browser; 1 is true; 0 is false")
flag.Parse()
if isOpen < 0 || isOpen > 1 {
fmt.Println("Error: -o must be 0 or 1")
return
}
checkPort(port)
genSSL()
genRsa()
exPath = getCurrentAbPath()
fmt.Println(exPath)
go func() {
version := ""
readVersion, versionErr := os.ReadFile(exPath + "/version.txt")
if versionErr != nil {
version = ""
} else {
version = string(readVersion)
}
readServerConfig()
mux := http.NewServeMux()
mime.TypeByExtension(".js")
mime.AddExtensionType(".js", "application/javascript")
log.Println(mime.TypeByExtension(".js"))
mux.HandleFunc("/application/logger", consoleHandler)
mux.Handle("/application/upload/", http.StripPrefix("/application/upload/", http.FileServer(http.Dir(filepath.FromSlash(exPath+"/upload")))))
mux.HandleFunc("/application/download-file", downloadHandler)
mux.HandleFunc("/application/hdcPublicKey", getHdcPublicKey)
mux.HandleFunc("/application/encryptHdcMsg", encryptHdcMsg)
mux.HandleFunc("/application/signatureHdcMsg", signatureHdcMsg)
mux.HandleFunc("/application/serverconfig", getServerConfig)
fs := http.FileServer(http.Dir(exPath + "/"))
mux.Handle("/application/", http.StripPrefix("/application/", cors(fs, version)))
go func() {
ser := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: mux,
}
log.Println(fmt.Sprintf("HTTPS[%d]服务启动", port))
err := ser.ListenAndServeTLS("cert/certFile.pem", "cert/keyFile.key")
CheckErr(err)
}()
go func() {
ser := &http.Server{
Addr: fmt.Sprintf(":%d", port+1),
Handler: mux,
}
log.Println(fmt.Sprintf("HTTP[%d]服务启动", port))
err := ser.ListenAndServe()
CheckErr(err)
}()
if isOpen == 1 {
open(fmt.Sprintf("https://localhost:%d/application", port))
}
}()
select {}
}
func getPidByPort(portNumber int) int {
resPid := -1
var out bytes.Buffer
cmdRes := exec.Command("cmd", "/c", fmt.Sprintf("netstat -ano -p tcp | findstr %d", portNumber))
cmdRes.Stdout = &out
cmdRes.Run()
cmdResStr := out.String()
findStr := regexp.MustCompile(`\s\d+\s`).FindAllString(cmdResStr, -1)
if len(findStr) > 0 {
pid, err := strconv.Atoi(strings.TrimSpace(findStr[0]))
if err != nil {
resPid = -1
} else {
resPid = pid
}
}
return resPid
}
type LoggerReq struct {
FileName string `json:"fileName"`
FileSize string `json:"fileSize"`
}
func consoleHandler(w http.ResponseWriter, r *http.Request) {
chekDir(exPath + "/logger")
var now = time.Now()
var fileName = fmt.Sprintf("%d-%d-%d", now.Year(), now.Month(), now.Day())
dst, err := os.OpenFile(exPath+"/logger/"+fileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND|os.O_SYNC, 0666)
CheckErr(err)
contentType := r.Header["Content-Type"]
if len(contentType) > 0 {
contentTypeName := contentType[0]
if strings.HasPrefix(contentTypeName, "application/json") {
decoder := json.NewDecoder(r.Body)
var req LoggerReq
decoder.Decode(&req)
dst.WriteString(fmt.Sprintf("%s %s (%s M)\n", now.Format("2006-01-02 15:04:05"), req.FileName, req.FileSize))
fmt.Fprintf(w, fmt.Sprintf("日志写入成功%s", exPath))
}
}
}
func getHdcPublicKey(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "text/json")
resp(&w)(true, 0, "success", map[string]interface{}{
"publicKey": hdcPublicKey,
})
}
func encryptHdcMsg(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "text/json")
hdcMsg := r.URL.Query().Get("message")
if len(hdcMsg) == 0 {
resp(&w)(false, -1, "Invalid message", nil)
return
}
signatures, err := rsa.SignPKCS1v15(nil, hdcPrivateKey, crypto.Hash(0), []byte(hdcMsg))
if err != nil {
resp(&w)(false, -1, "sign failed", nil)
} else {
resp(&w)(true, 0, "success", map[string]interface{}{
"signatures": base64.StdEncoding.EncodeToString(signatures),
})
}
}
func signatureHdcMsg(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "text/json")
hdcMsg := r.URL.Query().Get("message")
authType := RSA_3072_SHA512
signResult, err := RsaSignAndBase64(hdcMsg, authType)
if len(hdcMsg) == 0 {
resp(&w)(false, -1, "Invalid message", nil)
return
}
if err != nil {
resp(&w)(false, -1, "sign failed", nil)
} else {
resp(&w)(true, 0, "success", map[string]interface{}{
"signatures": signResult,
})
}
}
func parseServerConfigFile() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("parseServerConfigFile happen panic, content is %+v\n", r)
}
}()
fmt.Println("parseServerConfigFile")
serverConfigBuffer, err := os.ReadFile(exPath + "/server-config.json")
if err != nil {
fmt.Printf("read server config failed, error: %v\n", err)
return
}
serverConfig.Mux.Lock()
defer serverConfig.Mux.Unlock()
err = json.Unmarshal(serverConfigBuffer, &serverConfig)
if err != nil {
fmt.Printf("unmarshal server config failed, error: %v\n", err)
return
}
}
func getServerConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
serverConfig.Mux.RLock()
defer serverConfig.Mux.RUnlock()
data, err := json.Marshal(serverConfig)
if err != nil {
http.Error(w, "failed to marshal config: "+err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(data)
}
type ServerConfig struct {
ReportConfig ReportConfig `json:"reportConfig"`
TraceInfoConfig TraceInfoConfig `json:"traceInfoConfig"`
AdvertisingConfig AdvertisingConfig `json:"advertisingConfig"`
BulletinConfig BulletinConfig `json:"bulletinConfig"`
AiAssistantConfig AiAssistantConfig `json:"aiAssistantConfig"`
Mux sync.RWMutex `json:"-"`
}
type ReportConfig struct {
Switch bool `json:"switch"`
URL string `json:"url"`
}
type TraceInfoConfig struct {
Switch bool `json:"switch"`
Content string `json:"content"`
}
type AdvertisingConfig struct {
Switch bool `json:"switch"`
URL string `json:"url"`
}
type BulletinConfig struct {
Switch bool `json:"switch"`
Content string `json:"content"`
}
type AiAssistantConfig struct {
Switch bool `json:"switch"`
}
func loopUpdateServerConfig() {
loopTime := 5 * time.Minute
timer := time.NewTimer(loopTime)
for {
select {
case <-timer.C:
parseServerConfigFile()
}
timer.Reset(loopTime)
}
}
func readServerConfig() {
serverConfigBuffer, err := os.ReadFile(exPath + "/server-config.json")
if err != nil {
fmt.Printf("read server config failed, error: %v\n", err)
return
}
err = json.Unmarshal(serverConfigBuffer, &serverConfig)
if err != nil {
fmt.Printf("unmarshal server config failed, error: %v\n", err)
return
}
go loopUpdateServerConfig()
}
func mapToJson(m map[string]interface{}) (string, error) {
marshal, err := json.Marshal(m)
if err != nil {
return "", err
}
var str = string(marshal)
return str, nil
}
func jsonToMap(str string) (map[string]interface{}, error) {
var m = make(map[string]interface{})
err := json.Unmarshal([]byte(str), &m)
if err != nil {
return nil, err
}
return m, nil
}
func MkDir(path string) {
dir := path[0:strings.LastIndex(path, string(os.PathSeparator))]
if _, err := os.Stat(dir); err != nil {
os.MkdirAll(dir, os.ModePerm)
}
}
func resp(w *http.ResponseWriter) func(bool, int, string, map[string]interface{}) {
return func(success bool, code int, msg string, obj map[string]interface{}) {
toJson, err := mapToJson(map[string]interface{}{
"success": success,
"code": code,
"msg": msg,
"data": obj,
})
if err != nil {
errRes, _ := mapToJson(map[string]interface{}{
"success": false,
"code": -1,
"msg": err.Error(),
})
fmt.Fprintf(*w, errRes)
} else {
fmt.Fprintf(*w, toJson)
}
}
}
func get(url string) (*http.Response, error) {
jar, _ := cookiejar.New(nil)
c := &http.Client{
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
CheckRedirect: nil,
Jar: jar,
Timeout: time.Duration(3600) * time.Second,
}
return c.Get(url)
}
func clearOverdueFile() {
MkDir(filepath.FromSlash(fmt.Sprintf("./upload/")))
now := time.Now()
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
return
}
var checkDue = func(fileName string) bool {
f := getSuffixByUrl(fileName)
parseTime, err := time.ParseInLocation("20060102150405000", f.fileName, loc)
if err != nil {
return false
}
sub := now.Sub(parseTime)
if sub.Minutes() > 60 {
return true
}
return false
}
slash := filepath.FromSlash(fmt.Sprintf("./upload/"))
filepath.WalkDir(slash, func(path string, d fs.DirEntry, err error) error {
if checkDue(d.Name()) {
fmt.Println(now, "delete->", path, d.Name(), err)
os.Remove(path)
}
return nil
})
}
func getSuffixByUrl(u string) struct {
fileName string
suffix string
} {
lastIndex := strings.LastIndex(u, "/")
var f string
if lastIndex != -1 {
f = u[lastIndex:]
} else {
f = u
}
index := strings.LastIndex(f, ".")
if index != -1 {
return struct {
fileName string
suffix string
}{
f[0:index],
f[index:],
}
} else {
return struct {
fileName string
suffix string
}{
f,
"",
}
}
}
func downloadHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("content-type", "text/json")
clearOverdueFile()
contentType := r.Header["Content-Type"]
if len(contentType) > 0 {
contentTypeName := contentType[0]
if strings.HasPrefix(contentTypeName, "application/x-www-form-urlencoded") {
url := r.PostFormValue("url")
res, err := get(url)
if err != nil {
resp(&w)(false, -1, err.Error(), nil)
return
}
pth := filepath.FromSlash(fmt.Sprintf("/upload/%s%s", time.Now().Format("20060102150405000"), getSuffixByUrl(url).suffix))
MkDir("." + pth)
create, err := os.Create("." + pth)
if err != nil {
resp(&w)(false, -1, err.Error(), nil)
return
}
written, err := io.Copy(create, res.Body)
if err != nil {
resp(&w)(false, -1, err.Error(), nil)
return
}
fmt.Println(url, written)
resp(&w)(true, 0, "success", map[string]interface{}{
"url": pth,
"size": written,
})
return
}
}
resp(&w)(false, -1, "请求方式错误", nil)
}
func SplitLines(s string) []string {
var lines []string
sc := bufio.NewScanner(strings.NewReader(s))
for sc.Scan() {
lines = append(lines, sc.Text())
}
return lines
}
func readFileFirstLine(path string) string {
file, err := os.Open(path)
if err != nil {
return ""
}
defer file.Close()
readFile := bufio.NewReader(file)
line, readErr := readFile.ReadString('\n')
if readErr != nil || io.EOF == err {
return ""
}
return line
}
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func chekDir(path string) {
_, err := os.Stat(path)
if err != nil {
err := os.Mkdir(path, os.ModePerm)
if err != nil {
fmt.Printf("mkdir failed![%v]\n", err)
} else {
fmt.Printf("mkdir success!\n")
}
}
}
func CheckErr(err error) {
if err != nil {
log.Panicln(err)
}
}
func open(url string) error {
if isWindows() {
return openUrlWindows(url)
} else if isDarwin() {
return openUrlDarwin(url)
} else {
return openUrlOther(url)
}
}
func openUrlWindows(url string) error {
cmd := "cmd"
args := []string{"/c", "start", url}
return exec.Command(cmd, args...).Start()
}
func openUrlDarwin(url string) error {
var cmd = "open"
var args = []string{url}
return exec.Command(cmd, args...).Start()
}
func openUrlOther(url string) error {
var cmd = "xdg-open"
var args = []string{url}
return exec.Command(cmd, args...).Start()
}
func isWindows() bool {
return runtime.GOOS == "windows"
}
func isDarwin() bool {
return runtime.GOOS == "darwin"
}
func getCurrentAbPath() string {
dir := getExecutePath()
tmpDir, _ := filepath.EvalSymlinks(os.TempDir())
if strings.Contains(dir, tmpDir) {
return getCallerPath()
}
return dir
}
func getCallerPath() string {
var pth string
_, fName, _, ok := runtime.Caller(0)
if ok {
pth = path.Dir(fName)
}
return pth
}
func getExecutePath() string {
pth, err := os.Executable()
if err != nil {
log.Fatal(err)
}
res, _ := filepath.EvalSymlinks(filepath.Dir(pth))
return res
}
func checkPort(port int) {
if isWindows() {
pid := getPidByPort(port)
if pid != -1 {
res := exec.Command("cmd", "/c", fmt.Sprintf("taskkill /F /PID %d /T", pid))
res.Run()
}
}
}