forked from x/icebergs
326 lines
13 KiB
Go
326 lines
13 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
|
|
ice "shylinux.com/x/icebergs"
|
|
"shylinux.com/x/icebergs/base/aaa"
|
|
"shylinux.com/x/icebergs/base/cli"
|
|
"shylinux.com/x/icebergs/base/ctx"
|
|
"shylinux.com/x/icebergs/base/gdb"
|
|
"shylinux.com/x/icebergs/base/mdb"
|
|
"shylinux.com/x/icebergs/base/nfs"
|
|
"shylinux.com/x/icebergs/base/ssh"
|
|
"shylinux.com/x/icebergs/base/tcp"
|
|
kit "shylinux.com/x/toolkits"
|
|
)
|
|
|
|
func _serve_start(m *ice.Message) {
|
|
if m.Option(aaa.USERNAME) != "" {
|
|
aaa.UserRoot(m, m.Option(aaa.USERNAME), m.Option(aaa.USERNICK))
|
|
}
|
|
if cli.NodeInfo(m, kit.Select(ice.Info.Hostname, m.Option("nodename")), SERVER); m.Option(tcp.PORT) == tcp.RANDOM {
|
|
m.Option(tcp.PORT, m.Cmdx(tcp.PORT, aaa.RIGHT))
|
|
}
|
|
if runtime.GOOS == cli.WINDOWS {
|
|
m.Cmd(SPIDE, ice.OPS, kit.Format("http://localhost:%s/exit", m.Option(tcp.PORT))).Sleep("100ms")
|
|
}
|
|
m.Target().Start(m, m.OptionSimple(tcp.HOST, tcp.PORT)...)
|
|
m.Sleep("300ms")
|
|
for _, v := range kit.Split(m.Option(ice.DEV)) {
|
|
m.Cmd(SPACE, tcp.DIAL, ice.DEV, v, mdb.NAME, ice.Info.NodeName)
|
|
}
|
|
}
|
|
func _serve_main(m *ice.Message, w http.ResponseWriter, r *http.Request) bool {
|
|
const (
|
|
X_REAL_IP = "X-Real-Ip"
|
|
X_REAL_PORT = "X-Real-Port"
|
|
X_FORWARDED_FOR = "X-Forwarded-For"
|
|
INDEX_MODULE = "Index-Module"
|
|
MOZILLA = "Mozilla/5.0"
|
|
)
|
|
if r.Header.Get(INDEX_MODULE) == "" {
|
|
r.Header.Set(INDEX_MODULE, m.Prefix())
|
|
} else {
|
|
return true
|
|
}
|
|
if ip := r.Header.Get(X_REAL_IP); ip != "" {
|
|
if r.Header.Set(ice.MSG_USERIP, ip); r.Header.Get(X_REAL_PORT) != "" {
|
|
r.Header.Set(ice.MSG_USERADDR, ip+ice.DF+r.Header.Get(X_REAL_PORT))
|
|
}
|
|
} else if ip := r.Header.Get(X_FORWARDED_FOR); ip != "" {
|
|
r.Header.Set(ice.MSG_USERIP, kit.Split(ip)[0])
|
|
} else if strings.HasPrefix(r.RemoteAddr, "[") {
|
|
r.Header.Set(ice.MSG_USERIP, strings.Split(r.RemoteAddr, "]")[0][1:])
|
|
} else {
|
|
r.Header.Set(ice.MSG_USERIP, strings.Split(r.RemoteAddr, ice.DF)[0])
|
|
}
|
|
if m.Logs(r.Header.Get(ice.MSG_USERIP), r.Method, r.URL.String()); m.Config(LOGHEADERS) == ice.TRUE {
|
|
kit.Fetch(r.Header, func(k string, v []string) { m.Logs("Header", k, v) })
|
|
}
|
|
if r.Method == http.MethodGet && r.URL.Path != PP(SPACE) && !strings.HasPrefix(r.URL.Path, "/code/bash") {
|
|
repos := kit.Select(ice.INTSHELL, ice.VOLCANOS, strings.Contains(r.Header.Get(UserAgent), MOZILLA))
|
|
if p := path.Join(ice.USR, repos, r.URL.Path); r.URL.Path != ice.PS && nfs.ExistsFile(m, p) {
|
|
Render(m.Spawn(w, r), ice.RENDER_DOWNLOAD, p)
|
|
return false
|
|
} else if msg := gdb.Event(m.Spawn(w, r), SERVE_REWRITE, r.Method, r.URL.Path, path.Join(m.Conf(SERVE, kit.Keym(repos, nfs.PATH)), r.URL.Path), repos); msg.Option(ice.MSG_OUTPUT) != "" {
|
|
Render(msg, msg.Option(ice.MSG_OUTPUT), kit.List(msg.Optionv(ice.MSG_ARGS))...)
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
func _serve_handle(key string, cmd *ice.Command, m *ice.Message, w http.ResponseWriter, r *http.Request) {
|
|
if u, e := url.Parse(r.Header.Get(Referer)); e == nil && r.URL.Path != PP(SPACE) {
|
|
add := func(k, v string) { m.Logs("path", k, m.Option(k, v)) }
|
|
switch arg := strings.Split(strings.TrimPrefix(u.Path, ice.PS), ice.PS); arg[0] {
|
|
case "share":
|
|
add(arg[0], arg[1])
|
|
case "chat":
|
|
for i := 1; i < len(arg)-1; i += 2 {
|
|
add(arg[i], arg[i+1])
|
|
}
|
|
}
|
|
// gdb.Event(m, SERVE_PARSE, strings.Split(strings.TrimPrefix(u.Path, ice.PS), ice.PS))
|
|
kit.Fetch(u.Query(), func(k string, v []string) { m.Logs("Refer", k, v).Optionv(k, v) })
|
|
}
|
|
m.Option(ice.MSG_USERUA, r.Header.Get(UserAgent))
|
|
for k, v := range kit.ParseQuery(r.URL.RawQuery) {
|
|
kit.If(m.IsCliUA(), func() { v = kit.Simple(v, func(v string) (string, error) { return url.QueryUnescape(v) }) })
|
|
m.Optionv(k, v)
|
|
}
|
|
switch r.Header.Get(ContentType) {
|
|
case ContentJSON:
|
|
data := kit.UnMarshal(r.Body)
|
|
m.Logs(mdb.IMPORT, mdb.VALUE, kit.Format(data)).Optionv(ice.MSG_USERDATA, data)
|
|
kit.Fetch(data, func(k string, v ice.Any) { m.Optionv(k, v) })
|
|
default:
|
|
r.ParseMultipartForm(kit.Int64(kit.Select("4096", r.Header.Get(ContentLength))))
|
|
kit.Fetch(r.PostForm, func(k string, v []string) {
|
|
kit.If(m.IsCliUA(), func() { v = kit.Simple(v, func(v string) (string, error) { return url.QueryUnescape(v) }) })
|
|
m.Logs("Form", k, kit.Join(v, ice.SP)).Optionv(k, v)
|
|
})
|
|
}
|
|
kit.Fetch(r.Cookies(), func(k, v string) { m.Optionv(k, v) })
|
|
m.OptionDefault(ice.MSG_HEIGHT, "480", ice.MSG_WIDTH, "320")
|
|
m.Option(ice.MSG_USERUA, r.Header.Get(UserAgent))
|
|
m.Option(ice.MSG_USERIP, r.Header.Get(ice.MSG_USERIP))
|
|
m.Option(ice.MSG_USERADDR, kit.Select(r.RemoteAddr, r.Header.Get(ice.MSG_USERADDR)))
|
|
if m.Option(ice.MSG_USERWEB, _serve_domain(m)); m.Option(ice.POD) != "" {
|
|
m.Option(ice.MSG_USERPOD, m.Option(ice.POD))
|
|
}
|
|
if u := OptionUserWeb(m); strings.Contains(u.Host, tcp.LOCALHOST) {
|
|
m.Option(ice.MSG_USERHOST, tcp.PublishLocalhost(m, u.Scheme+"://"+u.Host))
|
|
} else {
|
|
m.Option(ice.MSG_USERHOST, u.Scheme+"://"+u.Host)
|
|
}
|
|
m.Option(ice.MSG_SESSID, kit.Select(m.Option(ice.MSG_SESSID), m.Option(CookieName(m.Option(ice.MSG_USERWEB)))))
|
|
if m.Optionv(ice.MSG_CMDS) == nil {
|
|
if p := strings.TrimPrefix(r.URL.Path, key); p != "" {
|
|
m.Optionv(ice.MSG_CMDS, strings.Split(p, ice.PS))
|
|
}
|
|
}
|
|
if cmds, ok := _serve_login(m, key, kit.Simple(m.Optionv(ice.MSG_CMDS)), w, r); ok {
|
|
defer func() { m.Cost(kit.Format("%s %v %v", r.URL.Path, cmds, m.FormatSize())) }()
|
|
m.Option(ice.MSG_OPTS, kit.Simple(m.Optionv(ice.MSG_OPTION), func(k string) bool { return !strings.HasPrefix(k, ice.MSG_SESSID) }))
|
|
if m.Detailv(m.PrefixKey(), cmds); len(cmds) > 1 && cmds[0] == ctx.ACTION {
|
|
m.ActionHand(cmd, key, cmds[1], cmds[2:]...)
|
|
} else {
|
|
m.CmdHand(cmd, key, cmds...)
|
|
}
|
|
}
|
|
Render(m, m.Option(ice.MSG_OUTPUT), m.Optionv(ice.MSG_ARGS))
|
|
}
|
|
func _serve_domain(m *ice.Message) string {
|
|
return kit.GetValid(
|
|
func() string { return kit.Select("", m.R.Header.Get(Referer), m.R.Method == http.MethodPost) },
|
|
func() string { return m.R.Header.Get("X-Host") },
|
|
func() string { return ice.Info.Domain },
|
|
func() string {
|
|
if b, e := regexp.MatchString("^[0-9.]+$", m.R.Host); b && e == nil {
|
|
return kit.Format("%s://%s:%s", kit.Select("https", ice.HTTP, m.R.TLS == nil), m.R.Host, m.Option(tcp.PORT))
|
|
}
|
|
return kit.Format("%s://%s", kit.Select("https", ice.HTTP, m.R.TLS == nil), m.R.Host)
|
|
},
|
|
)
|
|
}
|
|
func _serve_login(m *ice.Message, key string, cmds []string, w http.ResponseWriter, r *http.Request) ([]string, bool) {
|
|
if aaa.SessCheck(m, m.Option(ice.MSG_SESSID)); m.Option(ice.MSG_USERNAME) == "" && r.URL.Path != PP(SPACE) && !strings.HasPrefix(r.URL.Path, "/sync") {
|
|
gdb.Event(m, SERVE_LOGIN)
|
|
}
|
|
if _, ok := m.Target().Commands[WEB_LOGIN]; ok {
|
|
return cmds, !m.Target().Cmd(m, WEB_LOGIN, kit.Simple(key, cmds)...).IsErr()
|
|
} else if gdb.Event(m, SERVE_CHECK, key, cmds); m.IsErr() {
|
|
return cmds, false
|
|
} else if m.IsOk() {
|
|
return cmds, m.SetResult() != nil
|
|
} else {
|
|
return cmds, aaa.Right(m, key, cmds)
|
|
}
|
|
}
|
|
func _serve_address(m *ice.Message) string {
|
|
return kit.Format("http://localhost:%s", m.Option(tcp.PORT))
|
|
}
|
|
|
|
const (
|
|
SERVE_START = "serve.start"
|
|
SERVE_REWRITE = "serve.rewrite"
|
|
SERVE_PARSE = "serve.parse"
|
|
SERVE_LOGIN = "serve.login"
|
|
SERVE_CHECK = "serve.check"
|
|
SERVE_STOP = "serve.stop"
|
|
|
|
REQUIRE_MODULES = "require/modules/"
|
|
REQUIRE_USR = "require/usr/"
|
|
REQUIRE_SRC = "require/src/"
|
|
|
|
WEB_LOGIN = "_login"
|
|
DOMAIN = "domain"
|
|
INDEX = "index"
|
|
SSO = "sso"
|
|
)
|
|
const SERVE = "serve"
|
|
|
|
func init() {
|
|
Index.MergeCommands(ice.Commands{
|
|
"/exit": {Hand: func(m *ice.Message, arg ...string) { m.Cmdy(ice.EXIT) }},
|
|
SERVE: {Name: "serve name auto start", Help: "服务器", Actions: ice.MergeActions(ice.Actions{
|
|
ice.CTX_INIT: {Hand: func(m *ice.Message, arg ...string) { cli.NodeInfo(m, ice.Info.Pathname, WORKER) }},
|
|
cli.START: {Name: "start dev proto host port=9020 nodename username usernick", Hand: func(m *ice.Message, arg ...string) {
|
|
_serve_start(m)
|
|
}},
|
|
SERVE_START: {Hand: func(m *ice.Message, arg ...string) {
|
|
if m.Go(func() { m.Cmd(BROAD, SERVE, m.OptionSimple(tcp.PORT)) }); m.Option(ice.DEV) == "" {
|
|
m.Cmd(ssh.PRINTF, kit.Dict(nfs.CONTENT, ice.NL+ice.Render(m, ice.RENDER_QRCODE, tcp.PublishLocalhost(m, _serve_address(m))))).Cmd(ssh.PROMPT, kit.Dict(ice.LOG_DISABLE, ice.TRUE))
|
|
}
|
|
go func() {
|
|
opened := false
|
|
for i := 0; i < 3 && !opened; i++ {
|
|
m.Sleep("1s").Cmd(SPACE, kit.Dict(ice.LOG_DISABLE, ice.TRUE), func(values ice.Maps) {
|
|
kit.If(values[mdb.TYPE] == CHROME, func() { opened = true })
|
|
})
|
|
}
|
|
if opened {
|
|
return
|
|
}
|
|
cli.Opens(m, _serve_address(m))
|
|
}()
|
|
}},
|
|
SERVE_REWRITE: {Hand: func(m *ice.Message, arg ...string) {
|
|
if arg[0] != http.MethodGet {
|
|
return
|
|
}
|
|
switch arg[1] {
|
|
case ice.PS:
|
|
if arg[3] == ice.INTSHELL {
|
|
RenderIndex(m, arg[3])
|
|
} else {
|
|
RenderMain(m, "", "")
|
|
}
|
|
default:
|
|
if nfs.ExistsFile(m, arg[2]) {
|
|
m.RenderDownload(arg[2])
|
|
}
|
|
}
|
|
}},
|
|
SERVE_LOGIN: {Hand: func(m *ice.Message, arg ...string) {
|
|
if m.Option(ice.MSG_USERNAME) == "" && m.Config(tcp.LOCALHOST) == ice.TRUE && tcp.IsLocalHost(m, m.Option(ice.MSG_USERIP)) {
|
|
aaa.UserRoot(m)
|
|
}
|
|
}},
|
|
DOMAIN: {Hand: func(m *ice.Message, arg ...string) {
|
|
if len(arg) > 0 {
|
|
m.Config(tcp.LOCALHOST, ice.FALSE)
|
|
ice.Info.Domain = arg[0]
|
|
}
|
|
m.Echo(ice.Info.Domain)
|
|
}},
|
|
}, mdb.HashAction(
|
|
mdb.SHORT, mdb.NAME, mdb.FIELD, "time,status,name,proto,host,port", tcp.LOCALHOST, ice.TRUE, LOGHEADERS, ice.FALSE,
|
|
ice.INTSHELL, kit.Dict(nfs.PATH, ice.USR_INTSHELL, INDEX, ice.INDEX_SH, nfs.REPOS, "https://shylinux.com/x/intshell", nfs.BRANCH, nfs.MASTER),
|
|
ice.VOLCANOS, kit.Dict(nfs.PATH, ice.USR_VOLCANOS, INDEX, "page/index.html", nfs.REPOS, "https://shylinux.com/x/volcanos", nfs.BRANCH, nfs.MASTER),
|
|
), mdb.ClearHashOnExitAction(), ServeAction())},
|
|
PP(ice.INTSHELL): {Name: "/intshell/", Help: "命令行", Actions: aaa.WhiteAction(), Hand: func(m *ice.Message, arg ...string) {
|
|
RenderIndex(m, ice.INTSHELL, arg...)
|
|
}},
|
|
PP(ice.VOLCANOS): {Name: "/volcanos/", Help: "浏览器", Actions: aaa.WhiteAction(), Hand: func(m *ice.Message, arg ...string) {
|
|
RenderIndex(m, ice.VOLCANOS, arg...)
|
|
}},
|
|
PP(ice.PUBLISH): {Name: "/publish/", Help: "定制化", Actions: aaa.WhiteAction(), Hand: func(m *ice.Message, arg ...string) {
|
|
_share_local(m, ice.USR_PUBLISH, path.Join(arg...))
|
|
}},
|
|
PP(ice.PUBLISH, "demo"): {Name: "/publish/demo", Help: "定制化", Actions: aaa.WhiteAction(), Hand: func(m *ice.Message, arg ...string) {
|
|
m.RenderRedirect("/")
|
|
}},
|
|
PP(ice.REQUIRE): {Name: "/require/shylinux.com/x/volcanos/proto.js", Help: "代码库", Hand: func(m *ice.Message, arg ...string) {
|
|
if len(arg) < 4 {
|
|
m.RenderStatusBadRequest()
|
|
return
|
|
}
|
|
if path.Join(arg[:3]...) == ice.Info.Make.Module && nfs.ExistsFile(m, path.Join(arg[3:]...)) {
|
|
m.RenderDownload(path.Join(arg[3:]...))
|
|
return
|
|
}
|
|
cache := kit.Select(ice.USR_REQUIRE, m.Cmdx(cli.SYSTEM, "go", "env", "GOMODCACHE"))
|
|
p := path.Join(cache, path.Join(arg...))
|
|
if !nfs.ExistsFile(m, p) {
|
|
if p = path.Join(ice.USR_REQUIRE, path.Join(arg...)); !nfs.ExistsFile(m, p) {
|
|
ls := strings.SplitN(path.Join(arg[:3]...), ice.AT, 2)
|
|
if v := kit.Select(ice.Info.Gomod[ls[0]], ls, 1); v == "" {
|
|
m.Cmd(cli.SYSTEM, "git", "clone", "https://"+ls[0], path.Join(ice.USR_REQUIRE, path.Join(arg[:3]...)))
|
|
} else {
|
|
m.Cmd(cli.SYSTEM, "git", "clone", "-b", v, "https://"+ls[0], path.Join(ice.USR_REQUIRE, path.Join(arg[:3]...)))
|
|
}
|
|
}
|
|
}
|
|
m.RenderDownload(p)
|
|
}},
|
|
PP(REQUIRE_MODULES): {Name: "/require/modules/", Help: "依赖库", Hand: func(m *ice.Message, arg ...string) {
|
|
p := path.Join(ice.USR_NODE_MODULES, path.Join(arg...))
|
|
if !nfs.ExistsFile(m, p) {
|
|
m.Cmd(cli.SYSTEM, "npm", "install", arg[0], kit.Dict(cli.CMD_DIR, ice.USR))
|
|
}
|
|
m.RenderDownload(p)
|
|
}},
|
|
PP(REQUIRE_USR): {Name: "/require/usr/", Help: "代码库", Hand: func(m *ice.Message, arg ...string) {
|
|
_share_local(m, ice.USR, path.Join(arg...))
|
|
}},
|
|
PP(REQUIRE_SRC): {Name: "/require/src/", Help: "源代码", Actions: ice.MergeActions(ice.Actions{}, ctx.CmdAction(), aaa.RoleAction()), Hand: func(m *ice.Message, arg ...string) {
|
|
_share_local(m, ice.SRC, path.Join(arg...))
|
|
}},
|
|
PP(ice.HELP): {Name: "/help/", Help: "帮助", Actions: ice.MergeActions(ctx.CmdAction(), aaa.WhiteAction()), Hand: func(m *ice.Message, arg ...string) {
|
|
if len(arg) == 0 {
|
|
arg = append(arg, "tutor.shy")
|
|
}
|
|
if len(arg) > 0 && arg[0] != ctx.ACTION {
|
|
arg[0] = path.Join(ice.SRC_HELP, arg[0])
|
|
}
|
|
m.Cmdy("web.chat./cmd/", arg)
|
|
}},
|
|
})
|
|
ice.AddMerges(func(c *ice.Context, key string, cmd *ice.Command, sub string, action *ice.Action) (ice.Handler, ice.Handler) {
|
|
if strings.HasPrefix(sub, ice.PS) {
|
|
if sub = kit.Select(sub, PP(key), sub == ice.PS); action.Hand == nil {
|
|
action.Hand = func(m *ice.Message, arg ...string) { m.Cmdy(key, arg) }
|
|
}
|
|
actions := ice.Actions{}
|
|
for k, v := range cmd.Actions {
|
|
switch k {
|
|
case ctx.COMMAND, ice.RUN:
|
|
actions[k] = v
|
|
}
|
|
}
|
|
c.Commands[sub] = &ice.Command{Name: sub, Help: cmd.Help, Actions: actions, Hand: action.Hand}
|
|
}
|
|
return nil, nil
|
|
})
|
|
}
|
|
func ServeAction() ice.Actions {
|
|
return gdb.EventsAction(SERVE_START, SERVE_REWRITE, SERVE_PARSE, SERVE_LOGIN, SERVE_CHECK, SERVE_STOP)
|
|
}
|