package nodes import ( "fmt" rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges" "github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/cespare/xxhash" "github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/types" "io" "io/fs" "mime" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" ) // 文本mime-type列表 var textMimeMap = map[string]zero.Zero{ "application/atom+xml": {}, "application/javascript": {}, "application/x-javascript": {}, "application/json": {}, "application/rss+xml": {}, "application/x-web-app-manifest+json": {}, "application/xhtml+xml": {}, "application/xml": {}, "image/svg+xml": {}, "text/css": {}, "text/plain": {}, "text/javascript": {}, "text/xml": {}, "text/html": {}, "text/xhtml": {}, "text/sgml": {}, } // 调用本地静态资源 // 如果返回true,则终止请求 func (this *HTTPRequest) doRoot() (isBreak bool) { if this.web.Root == nil || !this.web.Root.IsOn { return } if len(this.uri) == 0 { this.write404() return true } rootDir := this.web.Root.Dir if this.web.Root.HasVariables() { rootDir = this.Format(rootDir) } if !filepath.IsAbs(rootDir) { rootDir = Tea.Root + Tea.DS + rootDir } requestPath := this.uri questionMarkIndex := strings.Index(this.uri, "?") if questionMarkIndex > -1 { requestPath = this.uri[:questionMarkIndex] } // 去掉其中的奇怪的路径 requestPath = strings.Replace(requestPath, "..\\", "", -1) // 进行URL Decode if this.web.Root.DecodePath { p, err := url.QueryUnescape(requestPath) if err == nil { requestPath = p } else { logs.Error(err) } } // 去掉前缀 stripPrefix := this.web.Root.StripPrefix if len(stripPrefix) > 0 { if stripPrefix[0] != '/' { stripPrefix = "/" + stripPrefix } requestPath = strings.TrimPrefix(requestPath, stripPrefix) if len(requestPath) == 0 || requestPath[0] != '/' { requestPath = "/" + requestPath } } filename := strings.Replace(requestPath, "/", Tea.DS, -1) filePath := "" if len(filename) > 0 && filename[0:1] == Tea.DS { filePath = rootDir + filename } else { filePath = rootDir + Tea.DS + filename } this.filePath = filePath // 用来记录日志 stat, err := os.Stat(filePath) if err != nil { _, isPathError := err.(*fs.PathError) if os.IsNotExist(err) || isPathError { if this.web.Root.IsBreak { this.write404() return true } return } else { this.write50x(err, http.StatusInternalServerError, true) logs.Error(err) return true } } if stat.IsDir() { indexFile, indexStat := this.findIndexFile(filePath) if len(indexFile) > 0 { filePath += Tea.DS + indexFile } else { if this.web.Root.IsBreak { this.write404() return true } return } this.filePath = filePath // stat again if indexStat == nil { stat, err = os.Stat(filePath) if err != nil { if os.IsNotExist(err) { if this.web.Root.IsBreak { this.write404() return true } return } else { this.write50x(err, http.StatusInternalServerError, true) logs.Error(err) return true } } } else { stat = indexStat } } // 响应header respHeader := this.writer.Header() // mime type contentType := "" if this.web.ResponseHeaderPolicy == nil || !this.web.ResponseHeaderPolicy.IsOn || !this.web.ResponseHeaderPolicy.ContainsHeader("CONTENT-TYPE") { ext := filepath.Ext(filePath) if len(ext) > 0 { mimeType := mime.TypeByExtension(ext) if len(mimeType) > 0 { semicolonIndex := strings.Index(mimeType, ";") mimeTypeKey := mimeType if semicolonIndex > 0 { mimeTypeKey = mimeType[:semicolonIndex] } if _, found := textMimeMap[mimeTypeKey]; found { if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 { charset := this.web.Charset.Charset if this.web.Charset.IsUpper { charset = strings.ToUpper(charset) } contentType = mimeTypeKey + "; charset=" + charset respHeader.Set("Content-Type", mimeTypeKey+"; charset="+charset) } else { contentType = mimeType respHeader.Set("Content-Type", mimeType) } } else { contentType = mimeType respHeader.Set("Content-Type", mimeType) } } } } // length var fileSize = stat.Size() // 支持 Last-Modified modifiedTime := stat.ModTime().Format("Mon, 02 Jan 2006 15:04:05 GMT") if len(respHeader.Get("Last-Modified")) == 0 { respHeader.Set("Last-Modified", modifiedTime) } // 支持 ETag eTag := "\"e" + fmt.Sprintf("%0x", xxhash.Sum64String(filename+strconv.FormatInt(stat.ModTime().UnixNano(), 10)+strconv.FormatInt(stat.Size(), 10))) + "\"" if len(respHeader.Get("ETag")) == 0 { respHeader.Set("ETag", eTag) } // 调用回调 this.onRequest() if this.writer.isFinished { return } // 支持 If-None-Match if this.requestHeader("If-None-Match") == eTag { // 自定义Header this.processResponseHeaders(http.StatusNotModified) this.writer.WriteHeader(http.StatusNotModified) return true } // 支持 If-Modified-Since if this.requestHeader("If-Modified-Since") == modifiedTime { // 自定义Header this.processResponseHeaders(http.StatusNotModified) this.writer.WriteHeader(http.StatusNotModified) return true } // 支持Range respHeader.Set("Accept-Ranges", "bytes") ifRangeHeaders, ok := this.RawReq.Header["If-Range"] supportRange := true if ok { supportRange = false for _, v := range ifRangeHeaders { if v == eTag || v == modifiedTime { supportRange = true break } } if !supportRange { respHeader.Del("Accept-Ranges") } } // 支持Range var ranges = []rangeutils.Range{} if supportRange { contentRange := this.RawReq.Header.Get("Range") if len(contentRange) > 0 { if fileSize == 0 { this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) return true } set, ok := httpRequestParseRangeHeader(contentRange) if !ok { this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) return true } if len(set) > 0 { ranges = set for k, r := range ranges { r2, ok := r.Convert(fileSize) if !ok { this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) return true } ranges[k] = r2 } } } else { respHeader.Set("Content-Length", strconv.FormatInt(fileSize, 10)) } } else { respHeader.Set("Content-Length", strconv.FormatInt(fileSize, 10)) } reader, err := os.OpenFile(filePath, os.O_RDONLY, 0444) if err != nil { this.write50x(err, http.StatusInternalServerError, true) return true } // 自定义Header this.processResponseHeaders(http.StatusOK) // 在Range请求中不能缓存 if len(ranges) > 0 { this.cacheRef = nil // 不支持缓存 } this.writer.Prepare(nil, fileSize, http.StatusOK, true) pool := this.bytePool(fileSize) buf := pool.Get() defer func() { _ = reader.Close() pool.Put(buf) }() if len(ranges) == 1 { respHeader.Set("Content-Range", ranges[0].ComposeContentRangeHeader(types.String(fileSize))) this.writer.WriteHeader(http.StatusPartialContent) ok, err := httpRequestReadRange(reader, buf, ranges[0].Start(), ranges[0].End(), func(buf []byte, n int) error { _, err := this.writer.Write(buf[:n]) return err }) if err != nil { logs.Error(err) return true } if !ok { this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) return true } } else if len(ranges) > 1 { boundary := httpRequestGenBoundary() respHeader.Set("Content-Type", "multipart/byteranges; boundary="+boundary) this.writer.WriteHeader(http.StatusPartialContent) for index, r := range ranges { if index == 0 { _, err = this.writer.WriteString("--" + boundary + "\r\n") } else { _, err = this.writer.WriteString("\r\n--" + boundary + "\r\n") } if err != nil { logs.Error(err) return true } _, err = this.writer.WriteString("Content-Range: " + r.ComposeContentRangeHeader(types.String(fileSize)) + "\r\n") if err != nil { logs.Error(err) return true } if len(contentType) > 0 { _, err = this.writer.WriteString("Content-Type: " + contentType + "\r\n\r\n") if err != nil { logs.Error(err) return true } } ok, err := httpRequestReadRange(reader, buf, r.Start(), r.End(), func(buf []byte, n int) error { _, err := this.writer.Write(buf[:n]) return err }) if err != nil { logs.Error(err) return true } if !ok { this.processResponseHeaders(http.StatusRequestedRangeNotSatisfiable) this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable) return true } } _, err = this.writer.WriteString("\r\n--" + boundary + "--\r\n") if err != nil { logs.Error(err) return true } } else { _, err = io.CopyBuffer(this.writer, reader, buf) if err != nil { logs.Error(err) return true } } // 设置成功 this.writer.SetOk() return true } // 查找首页文件 func (this *HTTPRequest) findIndexFile(dir string) (filename string, stat os.FileInfo) { if this.web.Root == nil || !this.web.Root.IsOn { return "", nil } if len(this.web.Root.Indexes) == 0 { return "", nil } for _, index := range this.web.Root.Indexes { if len(index) == 0 { continue } // 模糊查找 if strings.Contains(index, "*") { indexFiles, err := filepath.Glob(dir + Tea.DS + index) if err != nil { logs.Error(err) this.addError(err) continue } if len(indexFiles) > 0 { return filepath.Base(indexFiles[0]), nil } continue } // 精确查找 filePath := dir + Tea.DS + index stat, err := os.Stat(filePath) if err != nil || !stat.Mode().IsRegular() { continue } return index, stat } return "", nil }