package httpreverse import ( "b612.me/starlog" "bytes" "context" "encoding/base64" "fmt" "io/ioutil" "net/http" "net/http/httputil" "net/url" "strconv" "strings" "time" ) var version = "2.0.1" func (h *ReverseConfig) Run() error { err := h.init() if err != nil { return err } for key, proxy := range h.proxy { h.httpmux.HandleFunc(key, func(writer http.ResponseWriter, request *http.Request) { starlog.Infof("<%s> Req Path:%s Addr:%s UA:%s\n", h.Name, request.URL.Path, request.RemoteAddr, request.Header.Get("User-Agent")) if !h.BasicAuth(writer, request) { h.SetResponseHeader(writer) return } if !h.filter(writer, request) { h.SetResponseHeader(writer) writer.WriteHeader(403) if len(h.warnpagedata) != 0 { writer.Write(h.warnpagedata) return } writer.Write([]byte(` 403 Forbidden

403 Forbidden

You Are Not Allowed to Access This Page

Please Contact Site Administrator For Help


B612 HTTP REVERSE SERVER
`)) return } proxy.ServeHTTP(writer, request) }) } h.httpserver = http.Server{ Addr: fmt.Sprintf("%s:%d", h.Addr, h.Port), Handler: &h.httpmux, } starlog.Infoln(h.Name + " Listening on " + h.Addr + ":" + strconv.Itoa(h.Port)) if !h.UsingSSL { if err := h.httpserver.ListenAndServe(); err != http.ErrServerClosed { return err } return nil } if h.httpserver.ListenAndServeTLS(h.Cert, h.Key); err != http.ErrServerClosed { return err } return nil } func (h *ReverseConfig) Close() error { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() return h.httpserver.Shutdown(ctx) } func (h *ReverseConfig) init() error { h.proxy = make(map[string]*httputil.ReverseProxy) for key, val := range h.ReverseURL { h.proxy[key] = httputil.NewSingleHostReverseProxy(val) h.proxy[key].ModifyResponse = h.ModifyResponse() originalDirector := h.proxy[key].Director h.proxy[key].Director = func(req *http.Request) { originalDirector(req) h.ModifyRequest(req, val) } } return nil } func (h *ReverseConfig) SetResponseHeader(resp http.ResponseWriter) { resp.Header().Set("X-Powered-By", "B612.ME") resp.Header().Set("Server", "B612/"+version) resp.Header().Set("X-Proxy", "B612 Reverse Proxy") } func (h *ReverseConfig) ModifyResponse() func(*http.Response) error { return func(resp *http.Response) error { for _, v := range h.OutHeader { resp.Header.Set(v[0], v[1]) } resp.Header.Del("X-Powered-By") resp.Header.Del("Server") resp.Header.Del("X-Proxy") resp.Header.Set("X-Powered-By", "B612.ME") resp.Header.Set("Server", "B612/"+version) resp.Header.Set("X-Proxy", "B612 Reverse Proxy") if len(h.ReplaceList) != 0 && resp.ContentLength <= 20*1024*1024 && strings.Contains(resp.Header.Get("Content-Type"), "text") { body, err := ioutil.ReadAll(resp.Body) if err != nil { return err } strBody := string(body) replaceCount := -1 if h.ReplaceOnce { replaceCount = 1 } for _, v := range h.ReplaceList { strBody = strings.Replace(strBody, v[0], v[1], replaceCount) } body = []byte(strBody) resp.Body = ioutil.NopCloser(bytes.NewReader(body)) resp.ContentLength = int64(len(body)) resp.Header.Set("Content-Length", strconv.Itoa(len(body))) } return nil } } func (h *ReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) { if h.XForwardMode == 1 { req.Header.Set("X-Forwarded-For", strings.Split(req.RemoteAddr, ":")[0]) } else if h.XForwardMode == 2 { xforward := strings.Split(strings.TrimSpace(req.Header.Get("X-Forwarded-For")), ",") xforward = append(xforward, strings.Split(req.RemoteAddr, ":")[0]) req.Header.Set("X-Forwarded-For", strings.Join(xforward, ", ")) } for _, v := range h.Cookie { req.AddCookie(&http.Cookie{ Name: v[1], Value: v[2], Path: v[0], }) } host := h.Host if host == "" { host = remote.Host } targetQuery := remote.RawQuery req.URL.Scheme = remote.Scheme req.URL.Host = remote.Host req.Host = host req.URL.Path, req.URL.RawPath = joinURLPath(remote, req.URL) if targetQuery == "" || req.URL.RawQuery == "" { req.URL.RawQuery = targetQuery + req.URL.RawQuery } else { req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery } for _, v := range h.InHeader { req.Header.Set(v[0], v[1]) } } func (h *ReverseConfig) GiveBasicAuth(w http.ResponseWriter) { w.Header().Set("WWW-Authenticate", ` Basic realm="Please Enter Passwd"`) w.WriteHeader(401) w.Write([]byte(` 401 Authorization Required

401 Authorization Required


B612 HTTP SERVER
`)) } func (h *ReverseConfig) BasicAuth(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 { 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" { h.GiveBasicAuth(w) return false } } } return true } func (h *ReverseConfig) filter(w http.ResponseWriter, r *http.Request) bool { if len(h.blackip) == 0 && len(h.whiteip) == 0 { return true } if len(h.whiteip) != 0 { if _, ok := h.whiteip[strings.Split(r.RemoteAddr, ":")[0]]; ok { return true } for k, v := range h.whiteip { if match, _ := IPinRange2(k, v, strings.Split(r.RemoteAddr, ":")[0]); match { return true } } return false } if _, ok := h.blackip[strings.Split(r.RemoteAddr, ":")[0]]; ok { return false } for k, v := range h.blackip { if match, _ := IPinRange2(k, v, strings.Split(r.RemoteAddr, ":")[0]); match { return false } } return true } func joinURLPath(a, b *url.URL) (path, rawpath string) { if a.RawPath == "" && b.RawPath == "" { return singleJoiningSlash(a.Path, b.Path), "" } // Same as singleJoiningSlash, but uses EscapedPath to determine // whether a slash should be added apath := a.EscapedPath() bpath := b.EscapedPath() aslash := strings.HasSuffix(apath, "/") bslash := strings.HasPrefix(bpath, "/") switch { case aslash && bslash: return a.Path + b.Path[1:], apath + bpath[1:] case !aslash && !bslash: return a.Path + "/" + b.Path, apath + "/" + bpath } return a.Path + b.Path, apath + bpath } func singleJoiningSlash(a, b string) string { aslash := strings.HasSuffix(a, "/") bslash := strings.HasPrefix(b, "/") switch { case aslash && bslash: return a + b[1:] case !aslash && !bslash: return a + "/" + b } return a + b }