1// Copyright (C) 2022 Huawei Device Co., Ltd.
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package main
15
16//遇到报错请在当前目录下执行这个命令: go mod download golang.org/x/text
17import (
18	"bufio"
19	"bytes"
20	"crypto"
21	"crypto/rand"
22	"crypto/rsa"
23	"crypto/sha512"
24	"crypto/tls"
25	"crypto/x509"
26	"crypto/x509/pkix"
27	"encoding/base64"
28	"encoding/json"
29	"encoding/pem"
30	"flag"
31	"fmt"
32	"io"
33	"io/fs"
34	"log"
35	"math/big"
36	"mime"
37	"net"
38	"net/http"
39	"net/http/cookiejar"
40	"os"
41	"os/exec"
42	"path"
43	"path/filepath"
44	"regexp"
45	"runtime"
46	"strconv"
47	"strings"
48	"sync"
49	"time"
50)
51
52const HttpPort = 9000
53
54var exPath string
55var serveInfo string
56var msgPublishData MsgPublishData
57var hdcPublicKey string
58var hdcPrivateKey *rsa.PrivateKey
59
60// CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
61// CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
62func cors(fs http.Handler, version string) http.HandlerFunc {
63	return func(w http.ResponseWriter, r *http.Request) {
64		// return if you do not want the FileServer handle a specific request
65		r.Header.Add("Cross-Origin-Opener-Policy", "same-origin")
66		r.Header.Add("Cross-Origin-Embedder-Policy", "require-corp")
67		w.Header().Add("Cross-Origin-Opener-Policy", "same-origin")
68		w.Header().Add("Cross-Origin-Embedder-Policy", "require-corp")
69		w.Header().Set("Access-Control-Allow-Origin", "*")
70		w.Header().Set("Access-Control-Allow-Credentials", "true")
71		w.Header().Set("Access-Control-Allow-Headers", "x-requested-with, authorization, blade-auth") //*
72		w.Header().Set("Access-Control-Allow-Methods", "*")                                           //*
73		w.Header().Set("Access-Control-Max-Age", "3600")
74		w.Header().Set("data-version", version)
75		w.Header().Set("Cache-Control", "no-cache")
76		w.Header().Set("Pragma", "no-cache")
77		fs.ServeHTTP(w, r)
78	}
79}
80
81func exist(path string) bool {
82	_, err := os.Stat(path)
83	if err != nil {
84		if os.IsExist(err) {
85			return true
86		}
87		return false
88	}
89	return true
90}
91
92func genSSL() {
93	if exist("cert/keyFile.key") || exist("cert/certFile.pem") {
94		fmt.Println("keyFile.key exists")
95		return
96	}
97	max := new(big.Int).Lsh(big.NewInt(1), 128)
98	serialNumber, _ := rand.Int(rand.Reader, max)
99	subject := pkix.Name{
100		Organization:       []string{"www.smartperf.com"},
101		OrganizationalUnit: []string{"ITs"},
102		CommonName:         "www.smartperf.com",
103	}
104	certificate509 := x509.Certificate{
105		SerialNumber: serialNumber,
106		Subject:      subject,
107		NotBefore:    time.Now(),
108		NotAfter:     time.Now().AddDate(10, 0, 0),
109		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
110		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
111		IPAddresses:  []net.IP{net.ParseIP("127.0.0.1")},
112	}
113	chekDir("cert")
114	pk, _ := rsa.GenerateKey(rand.Reader, 1024)
115	derBytes, _ := x509.CreateCertificate(rand.Reader, &certificate509, &certificate509, &pk.PublicKey, pk)
116	certOut, _ := os.Create("cert/certFile.pem")
117	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
118	certOut.Close()
119	keyOut, _ := os.Create("cert/keyFile.key")
120	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pk)})
121	keyOut.Close()
122}
123
124func genRsa() {
125	// generate hdc private key
126	privateKey, err := rsa.GenerateKey(rand.Reader, 3072)
127	if err != nil {
128		fmt.Println("Generate hdc rsa private key failed")
129		return
130	}
131	hdcPrivateKey = privateKey
132
133	// generate hdc public key
134	publicKey := &privateKey.PublicKey
135	pkixPublicKey, err := x509.MarshalPKIXPublicKey(publicKey)
136	if err != nil {
137		fmt.Println(err)
138		return
139	}
140	publicKeyBlock := &pem.Block{
141		Type: "PUBLIC KEY",
142
143		Bytes: pkixPublicKey,
144	}
145	hdcPublicKey = string(pem.EncodeToMemory(publicKeyBlock))
146}
147
148func main() {
149	port := HttpPort
150	isOpen := 1
151	flag.IntVar(&port, "p", HttpPort, "The port number used")
152	flag.IntVar(&isOpen, "o", 1, "Whether to immediately open the website in your browser; 1 is true; 0 is false")
153	flag.Parse()
154	if isOpen < 0 || isOpen > 1 {
155		fmt.Println("Error: -o must be 0 or 1")
156		return
157	}
158	checkPort(port)
159	genSSL()
160	genRsa()
161	exPath = getCurrentAbPath()
162	fmt.Println(exPath)
163	go func() {
164		version := ""
165		readVersion, versionErr := os.ReadFile(exPath + "/version.txt")
166		if versionErr != nil {
167			version = ""
168		} else {
169			version = string(readVersion)
170		}
171		readReqServerConfig()
172		mux := http.NewServeMux()
173		mime.TypeByExtension(".js")
174		mime.AddExtensionType(".js", "application/javascript")
175		log.Println(mime.TypeByExtension(".js"))
176		mux.HandleFunc("/logger", consoleHandler)
177		mux.Handle("/application/upload/", http.StripPrefix("/application/upload/", http.FileServer(http.Dir(filepath.FromSlash(exPath+"/upload")))))
178		mux.HandleFunc("/application/download-file", downloadHandler)
179		mux.HandleFunc("/application/serverInfo", serverInfo)
180		mux.HandleFunc("/application/hdcPublicKey", getHdcPublicKey)
181		mux.HandleFunc("/application/encryptHdcMsg", encryptHdcMsg)
182		mux.HandleFunc("/application/signatureHdcMsg", signatureHdcMsg)
183		mux.HandleFunc("/application/messagePublish", getMsgPublish)
184		fs := http.FileServer(http.Dir(exPath + "/"))
185		mux.Handle("/application/", http.StripPrefix("/application/", cors(fs, version)))
186		go func() {
187			ser := &http.Server{
188				Addr:    fmt.Sprintf(":%d", port),
189				Handler: mux,
190			}
191			log.Println(fmt.Sprintf("HTTPS[%d]服务启动", port))
192			err := ser.ListenAndServeTLS("cert/certFile.pem", "cert/keyFile.key")
193			CheckErr(err)
194		}()
195		go func() {
196			ser := &http.Server{
197				Addr:    fmt.Sprintf(":%d", port+1),
198				Handler: mux,
199			}
200			log.Println(fmt.Sprintf("HTTP[%d]服务启动", port))
201			err := ser.ListenAndServe()
202			CheckErr(err)
203		}()
204		if isOpen == 1 {
205			open(fmt.Sprintf("https://localhost:%d/application", port))
206		}
207	}()
208	select {}
209}
210
211func getPidByPort(portNumber int) int {
212	resPid := -1
213	var out bytes.Buffer
214	cmdRes := exec.Command("cmd", "/c", fmt.Sprintf("netstat -ano -p tcp | findstr %d", portNumber))
215	cmdRes.Stdout = &out
216	cmdRes.Run()
217	cmdResStr := out.String()
218	findStr := regexp.MustCompile(`\s\d+\s`).FindAllString(cmdResStr, -1)
219	if len(findStr) > 0 {
220		pid, err := strconv.Atoi(strings.TrimSpace(findStr[0]))
221		if err != nil {
222			resPid = -1
223		} else {
224			resPid = pid
225		}
226	}
227	return resPid
228}
229
230type LoggerReq struct {
231	FileName string `json:"fileName"`
232	FileSize string `json:"fileSize"`
233}
234
235func consoleHandler(w http.ResponseWriter, r *http.Request) {
236	chekDir(exPath + "/logger")
237	var now = time.Now()
238	var fileName = fmt.Sprintf("%d-%d-%d", now.Year(), now.Month(), now.Day())
239	dst, err := os.OpenFile(exPath+"/logger/"+fileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND|os.O_SYNC, 0666)
240	CheckErr(err)
241	contentType := r.Header["Content-Type"]
242	if len(contentType) > 0 {
243		contentTypeName := contentType[0]
244		if strings.HasPrefix(contentTypeName, "application/json") {
245			decoder := json.NewDecoder(r.Body)
246			var req LoggerReq
247			decoder.Decode(&req)
248			dst.WriteString(fmt.Sprintf("%s %s (%s M)\n", now.Format("2006-01-02 15:04:05"), req.FileName, req.FileSize))
249			fmt.Fprintf(w, fmt.Sprintf("日志写入成功%s", exPath))
250		}
251	}
252}
253
254func serverInfo(w http.ResponseWriter, r *http.Request) {
255	w.Header().Set("Access-Control-Allow-Origin", "*")
256	w.Header().Set("request_info", serveInfo)
257	w.WriteHeader(200)
258}
259
260func getHdcPublicKey(w http.ResponseWriter, r *http.Request) {
261	w.Header().Set("Access-Control-Allow-Origin", "*")
262	w.Header().Set("Content-Type", "text/json")
263	resp(&w)(true, 0, "success", map[string]interface{}{
264		"publicKey": hdcPublicKey,
265	})
266}
267
268func encryptHdcMsg(w http.ResponseWriter, r *http.Request) {
269	w.Header().Set("Access-Control-Allow-Origin", "*")
270	w.Header().Set("Content-Type", "text/json")
271	hdcMsg := r.URL.Query().Get("message")
272	if len(hdcMsg) == 0 {
273		resp(&w)(false, -1, "Invalid message", nil)
274		return
275	}
276	signatures, err := rsa.SignPKCS1v15(nil, hdcPrivateKey, crypto.Hash(0), []byte(hdcMsg))
277	if err != nil {
278		resp(&w)(false, -1, "sign failed", nil)
279	} else {
280		resp(&w)(true, 0, "success", map[string]interface{}{
281			"signatures": base64.StdEncoding.EncodeToString(signatures),
282		})
283	}
284}
285
286func signatureHdcMsg(w http.ResponseWriter, r *http.Request) {
287	w.Header().Set("Access-Control-Allow-Origin", "*")
288	w.Header().Set("Content-Type", "text/json")
289	hdcMsg := r.URL.Query().Get("message")
290	if len(hdcMsg) == 0 {
291		resp(&w)(false, -1, "Invalid message", nil)
292		return
293	}
294	hashed := sha512.Sum512([]byte(hdcMsg))
295	signatures, err := rsa.SignPKCS1v15(nil, hdcPrivateKey, crypto.SHA512, hashed[:])
296	if err != nil {
297		resp(&w)(false, -1, "sign failed", nil)
298	} else {
299		resp(&w)(true, 0, "success", map[string]interface{}{
300			"signatures": base64.StdEncoding.EncodeToString(signatures),
301		})
302	}
303}
304
305func parseMsgPublishFile() {
306	defer func() {
307		if r := recover(); r != nil {
308			fmt.Printf("parseMsgPublishFile happen panic, content is %+v\n", r)
309		}
310	}()
311	msgPublishData.Mux.Lock()
312	defer msgPublishData.Mux.Unlock()
313	exist, err := PathExists(msgPublishData.FilePath)
314	if err != nil || !exist {
315		return
316	}
317	buf, err := os.ReadFile(msgPublishData.FilePath)
318	if err != nil {
319		fmt.Println("read fail", err)
320		return
321	}
322	msgPublishData.Msg = string(buf)
323}
324
325func getMsgPublish(w http.ResponseWriter, r *http.Request) {
326	w.Header().Set("Access-Control-Allow-Origin", "*")
327	w.Header().Set("Content-Type", "text/json")
328	msgPublishData.Mux.RLock()
329	data := msgPublishData.Msg
330	msgPublishData.Mux.RUnlock()
331	if len(data) == 0 {
332		resp(&w)(false, -1, "msg failed", nil)
333	} else {
334		resp(&w)(true, 0, "success", map[string]interface{}{
335			"data": data,
336		})
337	}
338}
339
340type ServerConfig struct {
341	ServeInfo      string `json:"ServeInfo"`
342	MsgPublishFile string `json:"MsgPublishFile"`
343}
344
345type MsgPublishData struct {
346	FilePath string
347	Msg      string
348	Mux      sync.RWMutex
349}
350
351func loopUpdateMsgPublishData() {
352	loopTime := 5 * time.Minute
353	timer := time.NewTimer(5 * time.Second)
354	for {
355		select {
356		case <-timer.C:
357			parseMsgPublishFile()
358		}
359		timer.Reset(loopTime)
360	}
361}
362
363func readReqServerConfig() {
364	serverConfigBuffer, err := os.ReadFile(exPath + "/server-config.json")
365	if err != nil {
366		return
367	}
368	var sc ServerConfig
369	err = json.Unmarshal(serverConfigBuffer, &sc)
370	if err != nil {
371		return
372	}
373	serveInfo = sc.ServeInfo
374	msgPublishData.Mux.Lock()
375	msgPublishData.FilePath = sc.MsgPublishFile
376	msgPublishData.Mux.Unlock()
377	go loopUpdateMsgPublishData()
378}
379
380func mapToJson(m map[string]interface{}) (string, error) {
381	marshal, err := json.Marshal(m)
382	if err != nil {
383		return "", err
384	}
385	var str = string(marshal)
386	return str, nil
387}
388func jsonToMap(str string) (map[string]interface{}, error) {
389	var m = make(map[string]interface{})
390	err := json.Unmarshal([]byte(str), &m)
391	if err != nil {
392		return nil, err
393	}
394	return m, nil
395}
396
397// MkDir 创建目录
398func MkDir(path string) {
399	dir := path[0:strings.LastIndex(path, string(os.PathSeparator))] //从文件路径获取目录
400	if _, err := os.Stat(dir); err != nil {                          //如果目录不存在,创建目录
401		os.MkdirAll(dir, os.ModePerm)
402	}
403}
404
405func resp(w *http.ResponseWriter) func(bool, int, string, map[string]interface{}) {
406	return func(success bool, code int, msg string, obj map[string]interface{}) {
407		toJson, err := mapToJson(map[string]interface{}{
408			"success": success,
409			"code":    code,
410			"msg":     msg,
411			"data":    obj,
412		})
413		if err != nil {
414			errRes, _ := mapToJson(map[string]interface{}{
415				"success": false,
416				"code":    -1,
417				"msg":     err.Error(),
418			})
419			fmt.Fprintf(*w, errRes)
420		} else {
421			fmt.Fprintf(*w, toJson)
422		}
423	}
424}
425
426func get(url string) (*http.Response, error) {
427	jar, _ := cookiejar.New(nil)
428	c := &http.Client{
429		Transport:     &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
430		CheckRedirect: nil,
431		Jar:           jar,
432		Timeout:       time.Duration(3600) * time.Second,
433	}
434	return c.Get(url)
435}
436
437func clearOverdueFile() {
438	MkDir(filepath.FromSlash(fmt.Sprintf("./upload/")))
439	now := time.Now()
440	loc, err := time.LoadLocation("Asia/Shanghai")
441	if err != nil {
442		return
443	}
444	var checkDue = func(fileName string) bool {
445		f := getSuffixByUrl(fileName)
446		parseTime, err := time.ParseInLocation("20060102150405000", f.fileName, loc)
447		if err != nil {
448			return false
449		}
450		sub := now.Sub(parseTime)
451		if sub.Minutes() > 60 { //bigger than 60 min flag due
452			return true
453		}
454		return false
455	}
456	slash := filepath.FromSlash(fmt.Sprintf("./upload/"))
457	filepath.WalkDir(slash, func(path string, d fs.DirEntry, err error) error {
458		if checkDue(d.Name()) {
459			fmt.Println(now, "delete->", path, d.Name(), err)
460			os.Remove(path)
461		}
462		return nil
463	})
464}
465func getSuffixByUrl(u string) struct {
466	fileName string
467	suffix   string
468} {
469	lastIndex := strings.LastIndex(u, "/")
470	var f string
471	if lastIndex != -1 {
472		f = u[lastIndex:]
473	} else {
474		f = u
475	}
476	index := strings.LastIndex(f, ".")
477	if index != -1 {
478		return struct {
479			fileName string
480			suffix   string
481		}{
482			f[0:index],
483			f[index:],
484		}
485	} else {
486		return struct {
487			fileName string
488			suffix   string
489		}{
490			f,
491			"",
492		}
493	}
494}
495
496func downloadHandler(w http.ResponseWriter, r *http.Request) {
497	w.Header().Set("content-type", "text/json")
498	clearOverdueFile()
499	contentType := r.Header["Content-Type"]
500	if len(contentType) > 0 {
501		contentTypeName := contentType[0]
502		if strings.HasPrefix(contentTypeName, "application/x-www-form-urlencoded") {
503			url := r.PostFormValue("url")
504			res, err := get(url)
505			if err != nil {
506				resp(&w)(false, -1, err.Error(), nil)
507				return
508			}
509			pth := filepath.FromSlash(fmt.Sprintf("/upload/%s%s", time.Now().Format("20060102150405000"), getSuffixByUrl(url).suffix))
510			MkDir("." + pth)
511			create, err := os.Create("." + pth)
512			if err != nil {
513				resp(&w)(false, -1, err.Error(), nil)
514				return
515			}
516			written, err := io.Copy(create, res.Body)
517			if err != nil {
518				resp(&w)(false, -1, err.Error(), nil)
519				return
520			}
521			fmt.Println(url, written)
522			pth = "/application" + pth
523			resp(&w)(true, 0, "success", map[string]interface{}{
524				"url":  pth,
525				"size": written,
526			})
527			return
528		}
529	}
530	resp(&w)(false, -1, "请求方式错误", nil)
531}
532
533func SplitLines(s string) []string {
534	var lines []string
535	sc := bufio.NewScanner(strings.NewReader(s))
536	for sc.Scan() {
537		lines = append(lines, sc.Text())
538	}
539	return lines
540}
541
542func readFileFirstLine(path string) string {
543	file, err := os.Open(path)
544	if err != nil {
545		return ""
546	}
547	defer file.Close()
548
549	readFile := bufio.NewReader(file)
550	line, readErr := readFile.ReadString('\n')
551	if readErr != nil || io.EOF == err {
552		return ""
553	}
554	return line
555}
556
557func PathExists(path string) (bool, error) {
558	_, err := os.Stat(path)
559	if err == nil {
560		return true, nil
561	}
562	if os.IsNotExist(err) {
563		return false, nil
564	}
565	return false, err
566}
567
568func chekDir(path string) {
569	_, err := os.Stat(path)
570	if err != nil {
571		err := os.Mkdir(path, os.ModePerm)
572		if err != nil {
573			fmt.Printf("mkdir failed![%v]\n", err)
574		} else {
575			fmt.Printf("mkdir success!\n")
576		}
577	}
578}
579func CheckErr(err error) {
580	if err != nil {
581		log.Panicln(err)
582	}
583}
584
585func open(url string) error {
586	if isWindows() {
587		return openUrlWindows(url)
588	} else if isDarwin() {
589		return openUrlDarwin(url)
590	} else {
591		return openUrlOther(url)
592	}
593}
594
595func openUrlWindows(url string) error {
596	cmd := "cmd"
597	args := []string{"/c", "start", url}
598	return exec.Command(cmd, args...).Start()
599}
600func openUrlDarwin(url string) error {
601	var cmd = "open"
602	var args = []string{url}
603	return exec.Command(cmd, args...).Start()
604}
605func openUrlOther(url string) error {
606	var cmd = "xdg-open"
607	var args = []string{url}
608	return exec.Command(cmd, args...).Start()
609}
610
611func isWindows() bool {
612	return runtime.GOOS == "windows"
613}
614func isDarwin() bool {
615	return runtime.GOOS == "darwin"
616}
617
618func getCurrentAbPath() string {
619	dir := getExecutePath()
620	tmpDir, _ := filepath.EvalSymlinks(os.TempDir())
621	if strings.Contains(dir, tmpDir) {
622		return getCallerPath()
623	}
624	return dir
625}
626
627func getCallerPath() string {
628	var pth string
629	_, fName, _, ok := runtime.Caller(0)
630	if ok {
631		pth = path.Dir(fName)
632	}
633	return pth
634}
635func getExecutePath() string {
636	pth, err := os.Executable()
637	if err != nil {
638		log.Fatal(err)
639	}
640	res, _ := filepath.EvalSymlinks(filepath.Dir(pth))
641	return res
642}
643
644func checkPort(port int) {
645	if isWindows() {
646		pid := getPidByPort(port)
647		if pid != -1 {
648			res := exec.Command("cmd", "/c", fmt.Sprintf("taskkill /F /PID %d /T", pid))
649			res.Run()
650		}
651	}
652}
653