diff --git a/server/api/cert.go b/server/api/cert.go index 34f43c67..f37dfaa4 100644 --- a/server/api/cert.go +++ b/server/api/cert.go @@ -1,169 +1,164 @@ package api import ( - "encoding/json" - "github.com/0xJacky/Nginx-UI/server/settings" - "github.com/0xJacky/Nginx-UI/server/tool" - "github.com/0xJacky/Nginx-UI/server/tool/nginx" - "github.com/gin-gonic/gin" - "github.com/gorilla/websocket" - "log" - "net/http" - "os" + "encoding/json" + "github.com/0xJacky/Nginx-UI/server/settings" + "github.com/0xJacky/Nginx-UI/server/tool" + "github.com/0xJacky/Nginx-UI/server/tool/nginx" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "log" + "net/http" + "os" ) func CertInfo(c *gin.Context) { - domain := c.Param("domain") + domain := c.Param("domain") - key, err := tool.GetCertInfo(domain) + key, err := tool.GetCertInfo(domain) - if err != nil { - ErrHandler(c, err) - return - } - - c.JSON(http.StatusOK, gin.H{ - "subject_name": key.Subject.CommonName, - "issuer_name": key.Issuer.CommonName, - "not_after": key.NotAfter, - "not_before": key.NotBefore, - }) + c.JSON(http.StatusOK, gin.H{ + "error": err, + "subject_name": key.Subject.CommonName, + "issuer_name": key.Issuer.CommonName, + "not_after": key.NotAfter, + "not_before": key.NotBefore, + }) } func IssueCert(c *gin.Context) { - domain := c.Param("domain") - var upGrader = websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - return true - }, - } + domain := c.Param("domain") + var upGrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, + } - // upgrade http to websocket - ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) - if err != nil { - log.Println(err) - return - } + // upgrade http to websocket + ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Println(err) + return + } - defer func(ws *websocket.Conn) { - err := ws.Close() - if err != nil { - log.Println(err) - return - } - }(ws) + defer func(ws *websocket.Conn) { + err := ws.Close() + if err != nil { + log.Println("defer websocket close err", err) + } + }(ws) - for { - // read - mt, message, err := ws.ReadMessage() - if err != nil { - break - } - if string(message) == "go" { - var m []byte + for { + // read + mt, message, err := ws.ReadMessage() + if err != nil { + break + } + if string(message) == "go" { + var m []byte - if settings.ServerSettings.Demo { - m, _ = json.Marshal(gin.H{ - "status": "error", - "message": "this feature is not available in demo", - }) - _ = ws.WriteMessage(mt, m) - return - } + if settings.ServerSettings.Demo { + m, _ = json.Marshal(gin.H{ + "status": "error", + "message": "this feature is not available in demo", + }) + _ = ws.WriteMessage(mt, m) + return + } - err = tool.IssueCert(domain) + err = tool.IssueCert(domain) - if err != nil { + if err != nil { - log.Println(err) + log.Println(err) - m, err = json.Marshal(gin.H{ - "status": "error", - "message": err.Error(), - }) + m, err = json.Marshal(gin.H{ + "status": "error", + "message": err.Error(), + }) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + return + } - err = ws.WriteMessage(mt, m) + err = ws.WriteMessage(mt, m) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + return + } - return - } + return + } - sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer") - _, err = os.Stat(sslCertificatePath) + sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer") + _, err = os.Stat(sslCertificatePath) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + return + } - log.Println("[found]", "fullchain.cer") - m, err = json.Marshal(gin.H{ - "status": "success", - "message": "[found] fullchain.cer", - }) + log.Println("[found]", "fullchain.cer") + m, err = json.Marshal(gin.H{ + "status": "success", + "message": "[found] fullchain.cer", + }) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + return + } - err = ws.WriteMessage(mt, m) + err = ws.WriteMessage(mt, m) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + return + } - sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key") - _, err = os.Stat(sslCertificateKeyPath) + sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key") + _, err = os.Stat(sslCertificateKeyPath) - if err != nil { - log.Println(err) - return - } + if err != nil { + log.Println(err) + return + } - log.Println("[found]", "cert key") - m, err = json.Marshal(gin.H{ - "status": "success", - "message": "[found] cert key", - }) + log.Println("[found]", "cert key") + m, err = json.Marshal(gin.H{ + "status": "success", + "message": "[found] cert key", + }) - if err != nil { - log.Println(err) - } + if err != nil { + log.Println(err) + } - err = ws.WriteMessage(mt, m) + err = ws.WriteMessage(mt, m) - if err != nil { - log.Println(err) - } + if err != nil { + log.Println(err) + } - log.Println("申请成功") - m, err = json.Marshal(gin.H{ - "status": "success", - "message": "申请成功", - "ssl_certificate": sslCertificatePath, - "ssl_certificate_key": sslCertificateKeyPath, - }) + log.Println("申请成功") + m, err = json.Marshal(gin.H{ + "status": "success", + "message": "申请成功", + "ssl_certificate": sslCertificatePath, + "ssl_certificate_key": sslCertificateKeyPath, + }) - if err != nil { - log.Println(err) - } + if err != nil { + log.Println(err) + } - err = ws.WriteMessage(mt, m) + err = ws.WriteMessage(mt, m) - if err != nil { - log.Println(err) - } - } - } + if err != nil { + log.Println(err) + } + } + } } diff --git a/server/api/domain.go b/server/api/domain.go index e9310dda..c335adfd 100644 --- a/server/api/domain.go +++ b/server/api/domain.go @@ -71,15 +71,9 @@ func GetDomain(c *gin.Context) { enabled = false } - content, err := ioutil.ReadFile(path) + config, err := nginx.ParseNgxConfig(path) if err != nil { - if os.IsNotExist(err) { - c.JSON(http.StatusNotFound, gin.H{ - "message": err.Error(), - }) - return - } ErrHandler(c, err) return } @@ -89,8 +83,8 @@ func GetDomain(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "enabled": enabled, "name": name, - "config": string(content), - "auto_cert": err == nil, + "config": config.BuildConfig(), + "tokenized": config, }) } @@ -150,7 +144,7 @@ func EnableDomain(c *gin.Context) { return } - // 测试配置文件,不通过则撤回启用 + // Test nginx config, if not pass then rollback. err = nginx.TestNginxConf() if err != nil { _ = os.Remove(enabledConfigFilePath) diff --git a/server/test/nextcloud_ngx.conf b/server/test/nextcloud_ngx.conf index 8dfd0cff..646e52b0 100644 --- a/server/test/nextcloud_ngx.conf +++ b/server/test/nextcloud_ngx.conf @@ -17,6 +17,10 @@ server { fastcgi_hide_header X-Powered-By; # Remove X-Powered-By, which is an information leak + if ($invalid_referer) { + return 403; + } + location = /robots.txt { allow all; log_not_found off; @@ -54,13 +58,13 @@ server { fastcgi_buffers 64 4K; # Enable gzip but do not remove ETag headers - gzip on; gzip_vary on; location /x/ {} gzip_comp_level 4; + gzip on; gzip_vary on; location /x/ {}gzip_comp_level 4; gzip_min_length 256;gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; # Uncomment if your server is build with the ngx_pagespeed module # This module is currently not supported. - #pagespeed off; + # pagespeed off; location / { if ( $http_user_agent ~ ^DavClnt ) { return 302 /remote.php/webdav/$is_args$args; diff --git a/server/tool/nginx/build_config.go b/server/tool/nginx/build_config.go new file mode 100644 index 00000000..d438dde5 --- /dev/null +++ b/server/tool/nginx/build_config.go @@ -0,0 +1,83 @@ +package nginx + +import ( + "bufio" + "fmt" + "strings" +) + +func buildComments(orig string, indent int) (content string) { + scanner := bufio.NewScanner(strings.NewReader(orig)) + for scanner.Scan() { + content += strings.Repeat("\t", indent) + "# " + scanner.Text() + "\n" + } + content = strings.TrimLeft(content, "\n") + return +} + +func (c *NgxConfig) BuildConfig() (content string) { + + // Custom + if c.Custom != "" { + content += fmtCode(c.Custom) + content += "\n\n" + } + + // Upstreams + for _, u := range c.Upstreams { + + upstream := "" + var comments string + for _, directive := range u.Directives { + if directive.Comments != "" { + comments = buildComments(directive.Comments, 1) + } + upstream += fmt.Sprintf("%s\t%s;\n", comments, directive.Orig()) + } + comments = buildComments(u.Comments, 1) + content += fmt.Sprintf("upstream %s {\n%s%s}\n\n", u.Name, comments, upstream) + } + + // Servers + for _, s := range c.Servers { + server := "" + + // directives + for _, directive := range s.Directives { + var comments string + if directive.Comments != "" { + comments = buildComments(directive.Comments, 1) + } + if directive.Directive == If { + server += fmt.Sprintf("%s%s\n", comments, fmtCodeWithIndent(directive.Params, 1)) + } else { + server += fmt.Sprintf("%s\t%s;\n", comments, directive.Orig()) + } + } + + if len(s.Directives) > 0 { + server += "\n" + } + + // locations + locations := "" + for _, location := range s.Locations { + locationContent := "" + scanner := bufio.NewScanner(strings.NewReader(location.Content)) + for scanner.Scan() { + locationContent += "\t\t" + scanner.Text() + "\n" + } + var comments string + if location.Comments != "" { + comments = buildComments(location.Comments, 1) + } + locations += fmt.Sprintf("%s\tlocation %s {\n%s\t}\n\n", comments, location.Path, locationContent) + } + + server += locations + + content += fmt.Sprintf("server {\n%s}\n\n", server) + } + + return +} diff --git a/server/tool/nginx/format_code.go b/server/tool/nginx/format_code.go new file mode 100644 index 00000000..bf80aeaf --- /dev/null +++ b/server/tool/nginx/format_code.go @@ -0,0 +1,49 @@ +package nginx + +import ( + "bufio" + "github.com/emirpasic/gods/stacks/linkedliststack" + "strings" +) + +func fmtCode(content string) (fmtContent string) { + fmtContent = fmtCodeWithIndent(content, 0) + return +} + +func fmtCodeWithIndent(content string, indent int) (fmtContent string) { + /* + Format content + 1. TrimSpace for each line + 2. use stack to count how many \t should add + */ + stack := linkedliststack.New() + + scanner := bufio.NewScanner(strings.NewReader(content)) + + for scanner.Scan() { + text := scanner.Text() + text = strings.TrimSpace(text) + + before := stack.Size() + + for _, char := range text { + matchParentheses(stack, char) + } + + after := stack.Size() + + fmtContent += strings.Repeat("\t", indent) + + if before == after { + fmtContent += strings.Repeat("\t", stack.Size()) + text + "\n" + } else { + fmtContent += text + "\n" + } + + } + + fmtContent = strings.Trim(fmtContent, "\n") + + return +} diff --git a/server/tool/nginx/parse.go b/server/tool/nginx/parse.go index 7b65395b..bfb45d9a 100644 --- a/server/tool/nginx/parse.go +++ b/server/tool/nginx/parse.go @@ -15,6 +15,7 @@ const ( Upstream = "upstream" CommentStart = "#" Empty = "" + If = "if" ) func matchParentheses(stack *linkedliststack.Stack, v int32) { @@ -44,20 +45,25 @@ func parseDirective(scanner *bufio.Scanner) (d NgxDirective) { return } - sep := len(text) - 1 - for k, v := range text { - if unicode.IsSpace(v) { - sep = k - break + if len(text) > 1 { + sep := len(text) - 1 + for k, v := range text { + if unicode.IsSpace(v) { + sep = k + break + } } - } - d.Directive = text[0:sep] - d.Params = text[sep:] + d.Directive = text[0:sep] + d.Params = text[sep:] + } else { + d.Directive = text + return + } stack := linkedliststack.New() - if d.Directive == Server || d.Directive == Upstream || d.Directive == Location { + if d.Directive == Server || d.Directive == Upstream || d.Directive == Location || d.Directive == If { // { } in one line // location = /.well-known/carddav { return 301 /remote.php/dav/; } if strings.Contains(d.Params, "{") { @@ -118,6 +124,10 @@ func ParseNgxConfig(filename string) (c *NgxConfig, err error) { c.parseUpstream(paramsScanner) case CommentStart: c.commentQueue.Enqueue(d.Params) + case Empty: + continue + default: + c.Custom += d.Orig() + "\n" } } @@ -125,5 +135,10 @@ func ParseNgxConfig(filename string) (c *NgxConfig, err error) { return nil, errors.Wrap(err, "error scanner in ParseNgxConfig") } + // Attach the rest of the comments to the last server + if len(c.Servers) > 0 { + c.Servers[len(c.Servers)-1].Comments += c.commentQueue.DequeueAllComments() + } + return c, nil } diff --git a/server/tool/nginx/tokenize.go b/server/tool/nginx/tokenize.go index 8f4bea1f..e8a63423 100644 --- a/server/tool/nginx/tokenize.go +++ b/server/tool/nginx/tokenize.go @@ -9,7 +9,6 @@ import ( func (c *NgxConfig) parseServer(scanner *bufio.Scanner) { server := NewNgxServer() - server.Directives = make(NgxDirectives) for scanner.Scan() { d := parseDirective(scanner) switch d.Directive { @@ -21,18 +20,21 @@ func (c *NgxConfig) parseServer(scanner *bufio.Scanner) { server.parseDirective(d) } } + // Attach the rest of the comments to the last location + if len(server.Locations) > 0 { + server.Locations[len(server.Locations)-1].Comments += server.commentQueue.DequeueAllComments() + } - // attach comments which are over the current server + // Attach comments which are over the current server server.Comments = c.commentQueue.DequeueAllComments() - c.Servers = append(c.Servers, *server) + c.Servers = append(c.Servers, server) } func (c *NgxConfig) parseUpstream(scanner *bufio.Scanner) { - upstream := NgxUpstream{} - upstream.Directives = make(NgxDirectives) - d := NgxDirective{} + upstream := &NgxUpstream{} for scanner.Scan() { + d := NgxDirective{} text := strings.TrimSpace(scanner.Text()) // escape empty line or comment line if len(text) < 1 || text[0] == '#' { @@ -51,7 +53,7 @@ func (c *NgxConfig) parseUpstream(scanner *bufio.Scanner) { d.Params = strings.Trim(text[sep:], ";") if d.Directive == Server { - upstream.Directives[d.Directive] = append(upstream.Directives[d.Directive], d) + upstream.Directives = append(upstream.Directives, &d) } else if upstream.Name == "" { upstream.Name = d.Directive } @@ -67,7 +69,14 @@ func (s *NgxServer) parseDirective(d NgxDirective) { // handle inline comments str, comments, _ := strings.Cut(orig, "#") - regExp := regexp.MustCompile("(\\S+?)\\s+{?(.+?)[;|}]") + if d.Directive == If { + d.Params = "if " + d.Params + d.Params = fmtCode(d.Params) + s.Directives = append(s.Directives, &d) + return + } + + regExp := regexp.MustCompile("(\\S+?)\\s+?{?(.+?)[;|}]") matchSlice := regExp.FindAllStringSubmatch(str, -1) for k, v := range matchSlice { @@ -90,7 +99,7 @@ func (s *NgxServer) parseDirective(d NgxDirective) { // trim right ';' d.TrimParams() // map[directive]=>[]Params - s.Directives[d.Directive] = append(s.Directives[d.Directive], d) + s.Directives = append(s.Directives, &d) } } @@ -100,9 +109,14 @@ func (s *NgxServer) parseDirective(d NgxDirective) { func (s *NgxServer) parseLocation(str string) { path, content, _ := strings.Cut(str, "{") + path = strings.TrimSpace(path) + content = strings.TrimSpace(content) content = strings.Trim(content, "}") - location := NgxLocation{ + + content = fmtCode(content) + + location := &NgxLocation{ Path: path, Content: content, } diff --git a/server/tool/nginx/type.go b/server/tool/nginx/type.go index 18086341..693e99c9 100644 --- a/server/tool/nginx/type.go +++ b/server/tool/nginx/type.go @@ -1,74 +1,74 @@ package nginx import ( - "github.com/emirpasic/gods/queues/linkedlistqueue" - "strings" + "github.com/emirpasic/gods/queues/linkedlistqueue" + "strings" ) type CommentQueue struct { - *linkedlistqueue.Queue + *linkedlistqueue.Queue } type NgxConfig struct { - FileName string `json:"file_name"` - Upstreams []NgxUpstream `json:"upstreams"` - Servers []NgxServer `json:"ngx_server"` - commentQueue *CommentQueue + FileName string `json:"file_name"` + Upstreams []*NgxUpstream `json:"upstreams"` + Servers []*NgxServer `json:"servers"` + Custom string `json:"custom"` + commentQueue *CommentQueue } type NgxServer struct { - ServerName string `json:"server_name"` - Directives NgxDirectives `json:"directives"` - Locations []NgxLocation `json:"locations"` - Comments string `json:"comments"` - commentQueue *CommentQueue + Directives []*NgxDirective `json:"directives"` + Locations []*NgxLocation `json:"locations"` + Comments string `json:"comments"` + commentQueue *CommentQueue } type NgxUpstream struct { - Name string `json:"name"` - Directives NgxDirectives `json:"directives"` - Comments string `json:"comments"` + Name string `json:"name"` + Directives []*NgxDirective `json:"directives"` + Comments string `json:"comments"` } type NgxDirective struct { - Directive string `json:"directive"` - Params string `json:"params"` - Comments string `json:"comments"` + Directive string `json:"directive"` + Params string `json:"params"` + Comments string `json:"comments"` } type NgxDirectives map[string][]NgxDirective type NgxLocation struct { - Path string `json:"path"` - Content string `json:"content"` - Comments string `json:"comments"` + Path string `json:"path"` + Content string `json:"content"` + Comments string `json:"comments"` } func (c *CommentQueue) DequeueAllComments() (comments string) { - for !c.Empty() { - comment, ok := c.Dequeue() + for !c.Empty() { + comment, ok := c.Dequeue() - if ok { - comments += strings.TrimSpace(comment.(string)) + "\n" - } - } + if ok { + comments += strings.TrimSpace(comment.(string)) + "\n" + } + } - return + return } func (d *NgxDirective) Orig() string { - return d.Directive + " " + d.Params + return d.Directive + " " + d.Params } func (d *NgxDirective) TrimParams() { - d.Params = strings.TrimRight(strings.TrimSpace(d.Params), ";") - return + d.Params = strings.TrimRight(strings.TrimSpace(d.Params), ";") + return } func NewNgxServer() *NgxServer { - return &NgxServer{commentQueue: &CommentQueue{linkedlistqueue.New()}} + return &NgxServer{commentQueue: &CommentQueue{linkedlistqueue.New()}} } func NewNgxConfig(filename string) *NgxConfig { - return &NgxConfig{FileName: filename, commentQueue: &CommentQueue{linkedlistqueue.New()}} + return &NgxConfig{FileName: filename, commentQueue: &CommentQueue{linkedlistqueue.New()}} }