star/httpserver/server.go
starainrt 7650951518 1.http server支持黑白路由名单和子文件夹挂载
2.http反向代理支持同一个端口按host区分不通服务,以及黑白路由名单支持
2025-06-19 23:47:39 +08:00

1056 lines
28 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package httpserver
import (
"b612.me/apps/b612/utils"
"b612.me/apps/b612/version"
"b612.me/starcrypto"
"b612.me/starlog"
"b612.me/starnet"
"b612.me/staros"
"context"
"crypto/tls"
"crypto/x509"
_ "embed"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
"io/ioutil"
"math"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
var ver = version.Version
type HttpServerCfgs func(cfg *HttpServerCfg)
type HttpServerCfg struct {
basicAuthUser string
basicAuthPwd string
EnvPath []string
SubFolder []string
uploadFolder string
logpath string
indexFile string
cert string
key string
allowHttpWithHttps bool
autoGenCert bool
addr string
port string
page404 string
page403 string
page401 string
protectAuthPage []string
disableMIME bool
ctx context.Context
hooks []ServerHook
httpDebug bool
noListPath []string
listPwd map[string]string
listSameForFile bool
// speed limit means xx bytes/s
speedlimit uint64
background string
mobildBackground string
blackpath Router
whitepath Router
blacklist []string
whitelist []string
envPath Router
}
type ServerHook struct {
MatchType []string `json:"match_type"`
Url string `json:"url"`
Timeout int `json:"timeout"`
MaxHookLength int `json:"max_hook_length"`
}
type HttpServer struct {
HttpServerCfg
}
//go:embed bootstrap.css
var bootStrap []byte
//go:embed jquery.js
var jquery []byte
//go:embed upload.html
var uploadPage []byte
//go:embed template.html
var templateHtml []byte
func WithHooks(hooks []ServerHook) HttpServerCfgs {
return func(cfg *HttpServerCfg) {
for k, v := range hooks {
if v.MaxHookLength == 0 {
hooks[k].MaxHookLength = 1024 * 1024
}
}
cfg.hooks = hooks
}
}
func WithTLSCert(cert, key string) HttpServerCfgs {
return func(cfg *HttpServerCfg) {
cfg.key = key
cfg.cert = cert
}
}
func WithUploadFolder(path string) HttpServerCfgs {
return func(cfg *HttpServerCfg) {
cfg.uploadFolder = path
}
}
func NewHttpServer(addr, port, path string, opts ...HttpServerCfgs) *HttpServer {
var server = HttpServer{
HttpServerCfg: HttpServerCfg{
addr: addr,
port: port,
EnvPath: []string{path},
},
}
for _, opt := range opts {
opt(&server.HttpServerCfg)
}
return &server
}
func (h *HttpServer) Run(ctx context.Context) error {
h.ctx = ctx
server := http.Server{
Addr: h.addr + ":" + h.port,
Handler: h,
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
switch conn := c.(type) {
case *tls.Conn:
return context.WithValue(ctx, "istls", true)
case *starnet.Conn:
return context.WithValue(ctx, "istls", conn)
}
return context.WithValue(ctx, "istls", false)
},
}
go func() {
select {
case <-h.ctx.Done():
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
server.Shutdown(ctx)
}
}()
if h.logpath != "" && starlog.GetWriter() == nil {
starlog.SetLogFile(h.logpath, starlog.Std, true)
}
netcards, err := net.Interfaces()
if err == nil {
for _, v := range netcards {
if strings.Contains(v.Flags.String(), "up") {
addrs, err := v.Addrs()
if err == nil {
for _, ip := range addrs {
starlog.Cyan("Name:%s\tIP:%s\n", v.Name, ip)
}
}
}
}
}
for idx, val := range h.EnvPath {
h.EnvPath[idx], err = filepath.Abs(val)
if len(h.SubFolder) > 0 {
h.envPath.AddLeaf(h.SubFolder[idx], h.EnvPath[idx])
}
}
if len(h.EnvPath) > 1 && len(h.EnvPath) != len(h.SubFolder) {
return errors.New("EnvPath and SubFolder length not match")
}
if err != nil {
starlog.Errorln("Failed to get abs path of", h.EnvPath)
return err
}
uconn, err := net.Dial("udp", "106.55.44.79:80")
if err == nil {
schema := "http://"
if h.cert != "" || h.autoGenCert {
schema = "https://"
}
starlog.Infof("Visit: %s%s:%s\n", schema, uconn.LocalAddr().(*net.UDPAddr).IP.String(), h.port)
uconn.Close()
}
starlog.Infoln("Listening on " + h.addr + ":" + h.port)
if h.cert == "" && !h.autoGenCert {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
return err
}
return nil
}
if !h.allowHttpWithHttps && !h.autoGenCert {
if err := server.ListenAndServeTLS(h.cert, h.key); err != http.ErrServerClosed {
return err
}
return nil
}
var lis net.Listener
if !h.autoGenCert {
lis, err = starnet.ListenTLS("tcp", h.addr+":"+h.port, h.cert, h.key, h.allowHttpWithHttps)
if err != nil {
return err
}
} else {
lis, err = starnet.ListenTLSWithConfig("tcp", h.addr+":"+h.port, &tls.Config{}, autoGenCert, h.allowHttpWithHttps)
if err != nil {
return err
}
}
defer lis.Close()
if err := server.Serve(lis); err != http.ErrServerClosed {
return err
}
return nil
}
var certCache = make(map[string]tls.Certificate)
var toolCa *x509.Certificate
var toolCaKey any
func autoGenCert(hostname string) *tls.Config {
if cert, ok := certCache[hostname]; ok {
return &tls.Config{Certificates: []tls.Certificate{cert}}
}
if toolCa == nil {
toolCa, toolCaKey = utils.ToolCert("")
}
cert, err := utils.GenerateTlsCert(utils.GenerateCertParams{
Country: "CN",
Organization: "B612 HTTP SERVER",
OrganizationUnit: "cert@b612.me",
CommonName: hostname,
Dns: []string{hostname},
KeyUsage: int(x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign),
ExtendedKeyUsage: []int{
int(x509.ExtKeyUsageServerAuth),
int(x509.ExtKeyUsageClientAuth),
},
IsCA: false,
StartDate: time.Now().Add(-24 * time.Hour),
EndDate: time.Now().AddDate(1, 0, 0),
Type: "RSA",
Bits: 2048,
CA: toolCa,
CAPriv: toolCaKey,
})
if err != nil {
return nil
}
certCache[hostname] = cert
return &tls.Config{Certificates: []tls.Certificate{cert}}
}
func (h *HttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.Listen(w, r)
}
func (h *HttpServer) Page404(w http.ResponseWriter) {
w.WriteHeader(404)
if h.page404 != "" {
data, err := os.ReadFile(h.page404)
if err == nil {
w.Write(data)
return
}
}
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">404 NOT FOUND</h1><hr ></body></html>`))
}
func (h *HttpServer) Page403(w http.ResponseWriter) {
w.WriteHeader(403)
if h.page403 != "" {
data, err := os.ReadFile(h.page403)
if err == nil {
w.Write(data)
return
}
}
w.Write([]byte(`
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>B612 HTTP SERVER</center>
</body>
</html>`))
}
func (h *HttpServer) Page401(w http.ResponseWriter) {
w.WriteHeader(401)
if h.page401 != "" {
data, err := os.ReadFile(h.page401)
if err == nil {
w.Write(data)
return
}
}
w.Write([]byte(`
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>B612 HTTP SERVER</center>
</body>
</html>`))
}
func (h *HttpServer) GiveBasicAuth(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", ` Basic realm="Please Enter Passwd"`)
h.Page401(w)
}
func (h *HttpServer) BasicAuth(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request) bool {
if h.basicAuthPwd != "" {
if len(h.protectAuthPage) != 0 {
for _, v := range h.protectAuthPage {
if !(strings.Index(r.URL.Path, v) == 0 || strings.Contains(r.URL.RawQuery, v)) {
return true
} else {
break
}
}
}
authHeader := strings.TrimSpace(r.Header.Get("Authorization"))
if len(authHeader) == 0 {
log.Noticeln("No Authed! Get Path is", r.URL.Path, r.RemoteAddr)
h.GiveBasicAuth(w)
return false
} else {
userAuth := base64.StdEncoding.EncodeToString([]byte(h.basicAuthUser + ":" + h.basicAuthPwd))
authStr := strings.Split(authHeader, " ")
if strings.TrimSpace(authStr[1]) != userAuth || strings.ToLower(strings.TrimSpace(authStr[0])) != "basic" {
log.Noticeln("Auth Failed! Get Path is", r.URL.Path, r.RemoteAddr, "pwd enter is", authHeader)
h.GiveBasicAuth(w)
return false
}
log.Infof("Path %s Authoried by %s:%s\n", r.URL.Path, r.RemoteAddr, authHeader)
}
}
return true
}
func (h *HttpServer) SetUpload(w http.ResponseWriter, r *http.Request, path string) bool {
if h.uploadFolder != "" {
if r.URL.Query().Get("bootstrap") == "true" {
w.Header().Set("Content-Type", "text/css")
w.Write(bootStrap)
return true
}
if r.URL.Query().Get("jquery") == "true" {
w.Header().Set("Content-Type", "application/javascript")
w.Write(jquery)
return true
}
if len(r.URL.Query()["upload"]) != 0 {
w.Header().Set("Content-Type", "text/html")
w.Write(uploadPage)
return true
}
}
return false
}
func (h *HttpServer) debugMode(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(200)
html := `<html><head><meta charset="utf-8"><title>B612 Http Server</title></head><body><h1 "style="text-align: center;">Debug Mode</h1><hr >%s</body></html>`
resp := "<h2>Url</h2>"
resp += "<p>" + r.Method + " " + r.URL.Path + "</p>"
resp += "<p> query " + r.URL.RawQuery + "</p>"
resp += "<p> fragment " + r.URL.Fragment + "</p>"
resp += "<p> FullUrl " + r.URL.String() + "</p>"
resp += "<h2>Query</h2>"
for k, v := range r.URL.Query() {
resp += fmt.Sprintf("<p>%s:%s</p>", k, v)
}
resp += "<h2>Header</h2>"
for key, val := range r.Header {
for _, v := range val {
resp += fmt.Sprintf("<p>%s:%s</p>", key, v)
}
}
resp += "<h2>Cookie</h2>"
for _, c := range r.Cookies() {
resp += fmt.Sprintf("<p>%s:%s</p>", c.Name, c.Value)
}
resp += "<h2>RemoteAddr</h2>"
resp += "<p>" + r.RemoteAddr + "</p>"
resp += "<h2>Proto</h2>"
resp += "<p>" + r.Proto + "</p>"
w.Write([]byte(fmt.Sprintf(html, resp)))
}
func (h *HttpServer) PermissionCheck(isTls string, w http.ResponseWriter, r *http.Request) bool {
checkPath := r.URL.Path
if strings.HasSuffix(checkPath, "/") && checkPath != "/" {
checkPath = strings.TrimSuffix(checkPath, "/")
}
if h.blackpath.Leaf != nil {
bp := h.blackpath.NearestLeafWithVal(checkPath)
if bp != nil {
if ppr, ok := bp.Val.(bool); ok {
if ppr || (!ppr && bp.FullPath == checkPath) {
starlog.Errorf(isTls+"%s %s From %s %s path is in black path, reject request \n", r.Method, checkPath, r.RemoteAddr, r.Header.Get("User-Agent"))
h.Page403(w)
return false
}
}
}
}
if h.whitepath.Leaf != nil {
bp := h.whitepath.NearestLeafWithVal(checkPath)
if bp == nil {
starlog.Errorf(isTls+"%s %s From %s %s path is not in white path, reject request \n", r.Method, checkPath, r.RemoteAddr, r.Header.Get("User-Agent"))
h.Page403(w)
return false
}
if ppr, ok := bp.Val.(bool); !ok {
starlog.Errorf(isTls+"%s %s From %s %s path is not in white path, reject request \n", r.Method, checkPath, r.RemoteAddr, r.Header.Get("User-Agent"))
h.Page403(w)
return false
} else {
if !ppr && bp.FullPath != checkPath {
starlog.Errorf(isTls+"%s %s From %s %s path is not in white path, reject request \n", r.Method, checkPath, r.RemoteAddr, r.Header.Get("User-Agent"))
h.Page403(w)
return false
}
}
}
return true
}
func (h *HttpServer) getEnvPath(r *http.Request) (string, string) {
if len(h.EnvPath) == 1 {
return "/", h.EnvPath[0]
}
checkPath := r.URL.Path
if strings.HasSuffix(checkPath, "/") && checkPath != "/" {
checkPath = strings.TrimSuffix(checkPath, "/")
}
if h.envPath.Leaf != nil {
leaf := h.envPath.NearestLeafWithVal(checkPath)
if leaf != nil && leaf.Val != nil {
if envPath, ok := leaf.Val.(string); ok {
return leaf.FullPath, envPath
}
}
}
return "", ""
}
func (h *HttpServer) getStrEnvPath(r string) (string, string) {
if len(h.EnvPath) == 1 {
return "/", h.EnvPath[0]
}
checkPath := r
if strings.HasSuffix(checkPath, "/") && checkPath != "/" {
checkPath = strings.TrimSuffix(checkPath, "/")
}
if h.envPath.Leaf != nil {
leaf := h.envPath.NearestLeafWithVal(checkPath)
if leaf != nil && leaf.Val != nil {
if envPath, ok := leaf.Val.(string); ok {
return leaf.FullPath, envPath
}
}
}
return "", ""
}
func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
log := starlog.Std.NewFlag()
log.SetShowFuncName(false)
log.SetShowOriginFile(false)
w.Header().Set("X-Powered-By", "B612.ME")
w.Header().Set("Server", "B612/"+ver)
if !h.BasicAuth(log, w, r) {
return
}
isTls := h.isTlsStr(r)
if !h.PermissionCheck(isTls, w, r) {
return
}
path := r.URL.Path
ua := r.Header.Get("User-Agent")
if h.httpDebug {
log.Infof("debug mode:%s %s From %s %s\n", r.Method, path, r.RemoteAddr, ua)
h.debugMode(w, r)
return
}
if h.uploadFolder != "" && path == "/recv" && len(r.URL.Query()["upload"]) != 0 {
h.uploadFile(w, r)
return
}
if h.SetUpload(w, r, path) {
return
}
prefix, envPath := h.getEnvPath(r)
if envPath == "" {
log.Errorf(isTls+"%s %s From %s %s No EnvPath Found\n", r.Method, path, r.RemoteAddr, ua)
h.Page404(w)
return
}
path = "/" + strings.TrimPrefix(path, prefix)
fullpath := filepath.Clean(filepath.Join(envPath, path))
{
//security check
if fullpath != envPath && !strings.HasPrefix(fullpath, envPath) {
log.Warningf("Invalid Path %s IP:%s Fullpath:%s\n", path, r.RemoteAddr, fullpath)
h.Page403(w)
return
}
}
if h.indexFile != "" && staros.IsFolder(fullpath) {
if staros.Exists(filepath.Join(fullpath, h.indexFile)) {
fullpath = filepath.Join(fullpath, h.indexFile)
path = filepath.Join(path, h.indexFile)
}
}
now := time.Now()
switch r.Method {
case "OPTIONS", "HEAD":
err := h.BuildHeader(w, r, fullpath)
if err != nil {
log.Warningf(isTls+"%s %s From %s %s %.2fs %v\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds(), err)
} else {
log.Infof(isTls+"%s %s From %s %s %.2fs \n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
}
case "GET":
err := h.BuildHeader(w, r, fullpath)
if err != nil {
log.Warningf(isTls+"GET Header Build Failed Path:%s IP:%s Err:%v\n", path, r.RemoteAddr, err)
}
err = h.ResponseGet(log, w, r, fullpath)
if err != nil {
log.Warningf(isTls+"%s %s From %s %s %.2fs %v\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds(), err)
return
}
log.Infof(isTls+"%s %s From %s %s %.2fs\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
default:
log.Errorf(isTls+"Invalid %s %s From %s %s %.2fs\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
return
}
}
func (h *HttpServer) CalcRange(r *http.Request) (int64, int64) {
var rangeStart, rangeEnd int64 = -1, -1
ranges := r.Header.Get("Range")
if ranges == "" {
return rangeStart, rangeEnd
}
if !strings.Contains(ranges, "bytes=") {
return rangeStart, rangeEnd
}
ranges = strings.TrimPrefix(ranges, "bytes=")
data := strings.Split(ranges, "-")
if len(data) == 0 {
return rangeStart, rangeEnd
}
rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
if len(data) > 1 {
rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
}
if rangeEnd == 0 {
rangeEnd = -1
}
return rangeStart, rangeEnd
}
func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpath string) error {
if r.Method == "OPTIONS" {
w.Header().Set("Allow", "OPTIONS,GET,HEAD")
w.Header().Set("Content-Length", "0")
}
w.Header().Set("Date", strings.ReplaceAll(time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST"), "UTC", "GMT"))
if staros.IsFolder(fullpath) {
return nil
}
mime := h.MIME(fullpath)
if h.disableMIME || mime == "" {
w.Header().Set("Content-Type", "application/download")
w.Header().Set("Content-Disposition", "attachment;filename="+filepath.Base(fullpath))
w.Header().Set("Content-Transfer-Encoding", "binary")
} else {
w.Header().Set("Content-Type", mime)
}
if staros.Exists(fullpath) {
finfo, err := os.Stat(fullpath)
if err != nil {
w.WriteHeader(502)
w.Write([]byte("Failed to Read " + fullpath + ",reason is " + err.Error()))
return err
}
w.Header().Set("Accept-Ranges", "bytes")
w.Header().Set("ETag", starcrypto.Md5Str([]byte(finfo.ModTime().String())))
w.Header().Set("Last-Modified", strings.ReplaceAll(finfo.ModTime().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST"), "UTC", "GMT"))
if r.Method != "OPTIONS" {
if _, ok := h.willHook(fullpath); ok {
return nil
}
start, end := h.CalcRange(r)
if start != -1 {
if end == -1 {
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size()-start, 10))
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(finfo.Size()-1, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
//w.Header().Set("Content-Length", strconv.FormatInt(fpinfo.Size()-rangeStart, 10))
} else {
w.Header().Set("Content-Length", strconv.FormatInt(end-start+1, 10))
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(end, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
//w.Header().Set("Content-Length", strconv.FormatInt(1+rangeEnd-rangeStart, 10))
}
} else {
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
}
}
}
return nil
}
func (h *HttpServer) willHook(fullpath string) (ServerHook, bool) {
finfo, err := os.Stat(fullpath)
if err != nil {
return ServerHook{}, false
}
if finfo.Size() < 1024*1024*10 && len(h.hooks) > 0 {
ext := h.GetExt(fullpath)
for _, hk := range h.hooks {
for _, e := range hk.MatchType {
if e == ext {
return hk, true
}
}
}
}
return ServerHook{}, false
}
func (h *HttpServer) ResponseGet(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if staros.IsFolder(fullpath) {
if len(h.listPwd) != 0 {
for k, v := range h.listPwd {
if strings.HasPrefix(r.URL.Path, k) {
if r.URL.Query().Get("list") == v {
return h.getFolder(log, w, r, fullpath)
}
}
}
}
if len(h.noListPath) != 0 {
for _, v := range h.noListPath {
if strings.HasPrefix(r.URL.Path, v) {
h.Page403(w)
return nil
}
}
}
return h.getFolder(log, w, r, fullpath)
}
if !h.listSameForFile {
return h.getFile(log, w, r, fullpath)
}
if len(h.listPwd) != 0 {
for k, v := range h.listPwd {
if strings.HasPrefix(r.URL.Path, k) {
if r.URL.Query().Get("list") == v {
return h.getFile(log, w, r, fullpath)
}
}
}
}
if len(h.noListPath) != 0 {
for _, v := range h.noListPath {
if strings.HasPrefix(r.URL.Path, v) {
h.Page403(w)
return nil
}
}
}
return h.getFile(log, w, r, fullpath)
}
type FileData struct {
Attr string `json:"attr"`
Name string `json:"name"`
Modified string `json:"modified"`
Size int64 `json:"size"`
Type string `json:"type"`
}
func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
dir, err := ioutil.ReadDir(fullpath)
if err != nil {
log.Errorf("Read Folder %s failed:%v\n", fullpath, err)
w.WriteHeader(403)
if r.Method == "HEAD" {
return err
}
w.Write([]byte("<h1>Cannot Access!</h1>"))
}
if r.Method != "GET" {
return nil
}
var upload string
if h.uploadFolder != "" {
prefix := r.URL.Path
if prefix == "/" {
prefix = ""
}
upload = `<a href=` + prefix + `/b612?upload=true>Upload Web Page Is Openned!</a>`
}
attr := r.URL.Query().Get("list")
if attr != "" {
attr = "/?list=" + attr
}
var fdatas = make([]FileData, 0, len(dir)+1)
if r.URL.Path != "/" {
p := r.URL.Path
if p[len(p)-1:] != "/" {
p += "/"
}
fdatas = append(fdatas, FileData{
Attr: p + ".." + attr,
Name: "..",
Modified: "-",
Size: -1,
Type: "上层文件夹",
})
}
if r.URL.Path == "/" {
r.URL.Path = ""
} else if r.URL.Path[len(r.URL.Path)-1:] == "/" {
r.URL.Path = r.URL.Path[0 : len(r.URL.Path)-1]
}
for _, v := range dir {
if v.Name() != "." || v.Name() != ".." {
if !v.IsDir() {
fdatas = append(fdatas, FileData{
Name: v.Name(),
Attr: r.URL.Path + "/" + v.Name(),
Modified: v.ModTime().Format("2006-01-02 15:04:05"),
Size: v.Size(),
Type: h.FileType(v.Name()),
})
} else {
fdatas = append(fdatas, FileData{
Name: v.Name() + "/",
Attr: r.URL.Path + "/" + v.Name() + attr,
Modified: v.ModTime().Format("2006-01-02 15:04:05"),
Size: -1,
Type: "文件夹",
})
}
}
}
tmpt, err := template.New("index").Parse(string(templateHtml))
if err != nil {
log.Errorf("Parse Template Error:%v\n", err)
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
jData, err := json.Marshal(fdatas)
if err != nil {
log.Errorf("Json Marshal Failed:%v\n", err)
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
if r.URL.Path == "" {
r.URL.Path = "/"
}
var bk, mbk string
if h.background != "" {
bk = `background: url('` + h.background + `') no-repeat center center fixed;`
}
if h.mobildBackground != "" {
mbk = `background: url('` + h.mobildBackground + `') no-repeat center center fixed;`
}
if h.mobildBackground == "" && h.background != "" {
mbk = bk
}
err = tmpt.Execute(w, map[string]interface{}{
"IdxTitle": r.URL.Path,
"Version": ver,
"Idx": "Index of " + r.URL.Path,
"Upload": template.HTML(upload),
"Data": template.JS(jData),
"Photo": template.CSS(bk),
"MobilePhoto": template.CSS(mbk),
})
if err != nil {
log.Errorf("Template Execute Failed:%v\n", err)
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
return nil
}
func (h *HttpServer) getSleepTime() time.Duration {
if h.speedlimit == 0 {
return 0
}
return time.Nanosecond * time.Duration(16384*1000*1000*1000/h.speedlimit) / 2
}
func (h *HttpServer) isTls(r *http.Request) bool {
if r.Context().Value("istls") != nil {
if v, ok := r.Context().Value("istls").(bool); ok {
if v {
return true
}
}
if v, ok := r.Context().Value("istls").(*starnet.Conn); ok {
return v.IsTLS()
}
}
return false
}
func (h *HttpServer) isTlsStr(r *http.Request) string {
if h.isTls(r) {
return "TLS "
}
return "PLN "
}
func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if !staros.Exists(fullpath) {
h.Page404(w)
return errors.New("File Not Found! 404 ERROR")
}
isTls := h.isTlsStr(r)
var lastCount int64
var lastDate time.Time = time.Now()
var currentCount int64
speedControl := func(count int) {
if h.speedlimit == 0 {
return
}
currentCount += int64(count)
for {
if time.Since(lastDate) < time.Second {
if uint64(currentCount-lastCount) > h.speedlimit {
time.Sleep(h.getSleepTime())
} else {
break
}
} else {
lastDate = time.Now()
lastCount = currentCount
break
}
}
}
//starlog.Debugln(r.Header)
startRange, endRange := h.CalcRange(r)
fp, err := os.Open(fullpath)
if err != nil {
log.Errorf("Failed to open file %s,reason:%v\n", r.URL.Path, err)
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
defer fp.Close()
var transferData int
defer func() {
if transferData != 0 {
var tani string
tani = fmt.Sprintf("%v Byte", transferData)
if f64 := float64(transferData) / 1024; f64 > 1 {
tani = fmt.Sprintf("%v KiB", f64)
if f64 = float64(f64) / 1024; f64 > 1 {
tani = fmt.Sprintf("%v MiB", f64)
if f64 = float64(f64) / 1024; f64 > 1 {
tani = fmt.Sprintf("%v GiB", f64)
}
}
}
log.Infof(isTls+"Tranfered File %s %d bytes (%s) to remote %v\n", r.URL.Path,
transferData, tani, r.RemoteAddr)
}
}()
if startRange == -1 {
hook, needCurl := h.willHook(fullpath)
if !needCurl {
w.WriteHeader(200)
for {
buf := make([]byte, 16384)
n, err := fp.Read(buf)
if n != 0 {
speedControl(n)
ns, err := w.Write(buf[:n])
transferData += ns
if err != nil {
log.Errorf(isTls+"Transfer File %s to Remote Failed:%v\n", fullpath, err)
return err
}
}
if err != nil {
if err == io.EOF {
break
}
log.Errorf("Read File %s Failed:%v\n", fullpath, err)
return err
}
}
return nil
}
data, err := os.ReadFile(fullpath)
if err != nil {
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
log.Errorf("Read File %s Failed:%v\n", fullpath, err)
return err
}
b64 := base64.StdEncoding.EncodeToString(data)
req, err := starnet.Curl(starnet.NewSimpleRequest(hook.Url,
"POST",
starnet.WithBytes(starnet.BuildPostForm(map[string]string{
"data": b64,
"ip": r.RemoteAddr,
})),
starnet.WithTimeout(time.Duration(hook.Timeout)*time.Millisecond)))
if err != nil {
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.WriteHeader(200)
ns, err := w.Write(data)
transferData += ns
if err != nil {
log.Errorf(isTls+"Transfer File %s to Remote Failed:%v\n", fullpath, err)
return err
}
return nil
}
recvData := req.Body().Bytes()
w.WriteHeader(200)
w.Header().Set("Content-Length", strconv.Itoa(len(recvData)))
ns, err := w.Write(recvData)
transferData += ns
if err != nil {
log.Errorf(isTls+"Transfer File %s to Remote Failed:%v\n", fullpath, err)
return err
}
return nil
}
log.Debugf(isTls+"206 transfer mode for %v %v start %v end %v\n", r.URL.Path, r.RemoteAddr, startRange, endRange)
w.WriteHeader(206)
fp.Seek(int64(startRange), 0)
count := startRange
for {
buf := make([]byte, 16384)
n, err := fp.Read(buf)
if err != nil {
if err == io.EOF {
break
}
log.Errorf(isTls+"Read File %s Failed:%v\n", r.URL.Path, err)
return err
}
speedControl(n)
if endRange == -1 {
ns, err := w.Write(buf[:n])
transferData += ns
if err != nil {
log.Errorf(isTls+"Transfer File %s to Remote Failed:%v\n", r.URL.Path, err)
return err
}
} else {
if count > endRange {
break
}
writeNum := n
if count+int64(n) > endRange {
writeNum = int(endRange - count + 1)
}
ns, err := w.Write(buf[0:writeNum])
if err != nil {
log.Errorln("Transfer Error:", err)
return err
}
transferData += ns
count += int64(n)
}
}
return nil
}
func (h *HttpServer) trimSize(size int64) string {
var tani string
tani = fmt.Sprintf("%v Byte", size)
if f64 := float64(size) / 1024; f64 > 1 {
tani = fmt.Sprintf("%.3f KiB", math.Trunc(f64*1e3+0.5)*1e-3)
if f64 = float64(f64) / 1024; f64 > 1 {
tani = fmt.Sprintf("%.3f MiB", math.Trunc(f64*1e3+0.5)*1e-3)
if f64 = float64(f64) / 1024; f64 > 1 {
tani = fmt.Sprintf("%.3f GiB", math.Trunc(f64*1e3+0.5)*1e-3)
}
}
}
return tani
}
func (h *HttpServer) uploadFile(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(200)
w.Write([]byte("USE POST METHOD!"))
return
}
r.ParseMultipartForm(10485760)
file, handler, err := r.FormFile("victorique")
if err != nil {
starlog.Errorf("Parse File From Form Failed:%v\n", err)
w.WriteHeader(502)
w.Write([]byte(err.Error()))
return
}
defer file.Close()
starlog.Noticef("Uploading %s From %s\n", handler.Filename, r.RemoteAddr)
_, envPath := h.getStrEnvPath(r.FormValue("path"))
if envPath == "" {
starlog.Errorf("No EnvPath Found For Upload File %s From %s\n", handler.Filename, r.RemoteAddr)
h.Page404(w)
return
}
os.MkdirAll(filepath.Join(envPath, h.uploadFolder), 0755)
f, err := os.OpenFile(filepath.Join(envPath, h.uploadFolder, handler.Filename), os.O_WRONLY|os.O_CREATE, 0755)
if err != nil {
starlog.Errorf("Open Local File %s Form %v Failed:%v\n", handler.Filename, r.RemoteAddr, err)
return
}
defer f.Close()
_, err = io.Copy(f, file)
if err != nil {
starlog.Errorf("Write File %s Form %v Failed:%v\n", handler.Filename, r.RemoteAddr, err)
return
}
starlog.Infof("Write File %s Form %v Finished\n", handler.Filename, r.RemoteAddr)
//fmt.Fprintf(w, `<html><body><p>%v</p><h2><a href="./b612?upload=true">Return To Web Page</a></h2></body></html>`, handler.Header)
}