From b1acc03bf5ccea2035ca0c0b5c66d6742fde6a33 Mon Sep 17 00:00:00 2001 From: y1n Date: Tue, 28 Jun 2022 16:36:29 +0800 Subject: [PATCH 1/4] update --- README.md | 7 +++++ cmd/client.go | 62 ++++++++++++++++++++++++++++++------------- cmd/main.go | 4 +-- go.mod | 9 +++++++ pkg/mitm/mitm.go | 26 ++++++++++++------ pkg/scf/handler.go | 40 ++++++++++++++++++++-------- pkg/scf/scfhandler.go | 29 +++++++++++++++----- pkg/scf/type.go | 2 +- pkg/viper/config.go | 35 ++++++++++++++++++++++++ 9 files changed, 167 insertions(+), 47 deletions(-) create mode 100644 pkg/viper/config.go diff --git a/README.md b/README.md index 374db72..70e792b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,13 @@ 白嫖云函数,构建自己的代理服务 +基于https://github.com/Sakurasan/scf-proxy修改 + +## 更新 +1. 增加http/2协议转发\ +需关闭ALPN,例如:Firefox about:config security.ssl.enable_alpn=false +2. 支持浏览器代理 +3. 原有的命令行方式改为yaml配置文件 ## 快速食用🍰 ``` sh build.sh diff --git a/cmd/client.go b/cmd/client.go index cbc75af..df2b79b 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -2,19 +2,20 @@ package main import ( "crypto/tls" - "flag" - "fmt" - "log" + "github.com/chroblert/jlog" + "golang.org/x/net/http2" + "net" "net/http" "net/http/httputil" "scf-proxy/pkg/mitm" "scf-proxy/pkg/scf" + "scf-proxy/pkg/viper" "sync" "time" ) const ( - HTTP_ADDR = "127.0.0.1:8080" + //HTTP_ADDR = "127.0.0.1:8080" SESSIONS_TO_CACHE = 10000 ) @@ -24,14 +25,20 @@ var ( ) func init() { - flag.StringVar(&clientPort, "p", "8080", "scf-proxy 客户端端口") - flag.StringVar(&scf.ScfApiProxyUrl, "scfurl", "", "scf-proxy 服务端地址") - flag.Parse() - if scf.ScfApiProxyUrl == "" { - panic("scf-proxy 服务端地址为空") - } - fmt.Println(scf.ScfApiProxyUrl) - + _ = jlog.SetLogFullPath("logs/client.log", 0755, 0755) + jlog.SetStoreToFile(true) + jlog.SetMaxSizePerLogFile("10MB") + jlog.SetMaxStoreDays(30) + jlog.SetLogCount(30) + jlog.IsIniCreateNewLog(true) + jlog.SetVerbose(true) + //flag.StringVar(&clientPort, "p", "1080", "scf-proxy 客户端端口") + //flag.StringVar(&scf.ScfApiProxyUrl, "scfurl", "", "scf-proxy 服务端地址") + //flag.Parse() + //if scf.ScfApiProxyUrl == "" { + // panic("scf-proxy 服务端地址为空") + //} + scf.ScfApiProxyUrl, clientPort = viper.YamlConfig() } func main() { @@ -39,8 +46,10 @@ func main() { runHTTPServer() // Uncomment the below line to keep the server running exampleWg.Wait() + //defer jlog.Flush() // Output: + } func runHTTPServer() { @@ -66,20 +75,36 @@ func runHTTPServer() { rp := &httputil.ReverseProxy{ Director: func(req *http.Request) { - log.Printf("Processing request to: %s", req.URL) + jlog.Infof("Processing request to: %s", req.URL) }, - Transport: &http.Transport{ + //Transport: &http.Transport{ + // //AllowHTTP: true, + // TLSClientConfig: &tls.Config{ + // // Use a TLS session cache to minimize TLS connection establishment + // // Requires Go 1.3+ + // ClientSessionCache: tls.NewLRUClientSessionCache(SESSIONS_TO_CACHE), + // }, + // //DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { + // // return net.Dial(network, addr) + // //}, + //}, + Transport: &http2.Transport{ + AllowHTTP: true, TLSClientConfig: &tls.Config{ // Use a TLS session cache to minimize TLS connection establishment // Requires Go 1.3+ ClientSessionCache: tls.NewLRUClientSessionCache(SESSIONS_TO_CACHE), }, + DialTLS: func(netw, addr string, cfg *tls.Config) (net.Conn, error) { + return net.Dial(netw, addr) + }, }, + FlushInterval: -1, } handler, err := mitm.Wrap(rp, cryptoConfig) if err != nil { - log.Fatalf("Unable to wrap reverse proxy: %s", err) + jlog.Fatalf("Unable to wrap reverse proxy: %s", err) } server := &http.Server{ @@ -88,14 +113,15 @@ func runHTTPServer() { ReadTimeout: 1 * time.Minute, WriteTimeout: 1 * time.Minute, } - + _ = http2.ConfigureServer(server, nil) go func() { - log.Printf("About to start HTTP proxy at :%s", clientPort) + jlog.Infof("About to start HTTP proxy at :%s\n", clientPort) if err := server.ListenAndServe(); err != nil { - log.Fatalf("Unable to start HTTP proxy: %s", err) + jlog.Fatalf("Unable to start HTTP proxy: %s", err) } exampleWg.Done() }() return + //} } diff --git a/cmd/main.go b/cmd/main.go index 375f424..9dd92c8 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -5,13 +5,11 @@ import ( "context" "encoding/json" "fmt" - - "scf-proxy/pkg/scf" - "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" "github.com/tencentyun/scf-go-lib/cloudfunction" "github.com/tencentyun/scf-go-lib/events" + "scf-proxy/pkg/scf" ) func main() { diff --git a/go.mod b/go.mod index a650ed0..c6dfc08 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,15 @@ module scf-proxy go 1.15 require ( + github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect + github.com/chroblert/jlog v0.0.8 + github.com/coreos/etcd v3.3.10+incompatible // indirect + github.com/coreos/go-etcd v2.0.0+incompatible // indirect github.com/sirupsen/logrus v1.8.1 + github.com/spf13/viper v1.12.0 github.com/tencentyun/scf-go-lib v0.0.0-20200624065115-ba679e2ec9c9 + github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect + github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect + golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 + golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect ) diff --git a/pkg/mitm/mitm.go b/pkg/mitm/mitm.go index ccaa8fb..ea298f4 100644 --- a/pkg/mitm/mitm.go +++ b/pkg/mitm/mitm.go @@ -3,13 +3,15 @@ package mitm import ( "crypto/tls" "fmt" + "github.com/chroblert/jlog" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" "io" - "log" "net/http" - "net/http/httputil" "scf-proxy/pkg/scf" "strings" "sync" + "time" ) const ( @@ -30,9 +32,14 @@ type HandlerWrapper struct { } func Wrap(handler http.Handler, cryptoConf *CryptoConfig) (*HandlerWrapper, error) { + h2s := &http2.Server{ + IdleTimeout: time.Second * 60, + } wrapper := &HandlerWrapper{ - cryptoConf: cryptoConf, - wrapped: handler, + cryptoConf: cryptoConf, + //wrapped: handler, + //http/2 支持 + wrapped: h2c.NewHandler(handler, h2s), dynamicCerts: NewCache(), } err := wrapper.initCrypto() @@ -48,8 +55,9 @@ func (wrapper *HandlerWrapper) ServeHTTP(resp http.ResponseWriter, req *http.Req wrapper.intercept(resp, req) } else { // wrapper.wrapped.ServeHTTP(resp, req) - reqdump, _ := httputil.DumpRequest(req, true) - fmt.Println("dump req:", string(reqdump)) + //reqdump, _ := httputil.DumpRequest(req, true) + //fmt.Println("dump req:", string(reqdump)) + //jlog.Info(string(reqdump)) scf.HandlerHttp(resp, req) } } @@ -73,6 +81,8 @@ func (wrapper *HandlerWrapper) intercept(resp http.ResponseWriter, req *http.Req return } tlsConfig := makeConfig(wrapper.cryptoConf.ServerTLSConfig) + //HTTP/2 支持 + tlsConfig.NextProtos = []string{"h2"} tlsConfig.Certificates = []tls.Certificate{*cert} tlsConnIn := tls.Server(connIn, tlsConfig) @@ -91,7 +101,7 @@ func (wrapper *HandlerWrapper) intercept(resp http.ResponseWriter, req *http.Req go func() { err = http.Serve(listener, handler) if err != nil && err != io.EOF { - log.Printf("Error serving mitm'ed connection: %s", err) + jlog.Printf("Error serving mitm'ed connection: %s", err) } }() @@ -116,7 +126,7 @@ func hostIncludingPort(req *http.Request) (host string) { } func respBadGateway(resp http.ResponseWriter, msg string) { - log.Println(msg) + jlog.Error(msg) resp.WriteHeader(502) resp.Write([]byte(msg)) } diff --git a/pkg/scf/handler.go b/pkg/scf/handler.go index 5f3a023..a70ad24 100644 --- a/pkg/scf/handler.go +++ b/pkg/scf/handler.go @@ -4,10 +4,11 @@ import ( "bytes" "encoding/base64" "encoding/json" + "github.com/chroblert/jlog" "io/ioutil" - "log" "net/http" "net/http/httputil" + "strings" ) var ( @@ -17,7 +18,7 @@ var ( func HandlerHttp(w http.ResponseWriter, r *http.Request) { dumpReq, err := httputil.DumpRequest(r, true) if err != nil { - log.Println(err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } @@ -26,46 +27,63 @@ func HandlerHttp(w http.ResponseWriter, r *http.Request) { URL: r.URL.String(), Content: base64.StdEncoding.EncodeToString(dumpReq), } + //jlog.Println(event.URL, event.Content) bytejson, err := json.Marshal(event) if err != nil { - log.Println(err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } req, err := http.NewRequest("POST", ScfApiProxyUrl, bytes.NewReader(bytejson)) if err != nil { - log.Println(err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } resp, err := http.DefaultClient.Do(req) if err != nil { - log.Println("client.Do()", err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } bytersp, _ := ioutil.ReadAll(resp.Body) - var respevent RespEvent if err := json.Unmarshal(bytersp, &respevent); err != nil { - log.Println(err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } if resp.StatusCode > 0 && resp.StatusCode != 200 { - log.Println(err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } - retByte, err := base64.StdEncoding.DecodeString(respevent.Data) + //处理头+内容 + resp1 := strings.Split(respevent.Data, "^") + respHeaders, err := base64.StdEncoding.DecodeString(resp1[0]) + respBody, err := base64.StdEncoding.DecodeString(resp1[1]) + //retByte, err := base64.StdEncoding.DecodeString(respevent.Data) if err != nil { - log.Println(err) + jlog.Error(err) w.WriteHeader(http.StatusServiceUnavailable) return } resp.Body.Close() + respHeadersMap := make(map[string][]string) + err = json.Unmarshal(respHeaders, &respHeadersMap) + for k, v := range respHeadersMap { + var s []string + for _, val := range v { + s = append(s, val) + } + //jlog.Printf("%s:%s\n", k, s[0]) + w.Header().Set(k, s[0]) + } + w.Write(respBody) - w.Write(retByte) + //w.Write(retByte) return } + +//} diff --git a/pkg/scf/scfhandler.go b/pkg/scf/scfhandler.go index 6d23a82..70201e9 100644 --- a/pkg/scf/scfhandler.go +++ b/pkg/scf/scfhandler.go @@ -7,18 +7,17 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" + log "github.com/sirupsen/logrus" + "github.com/tencentyun/scf-go-lib/events" "io/ioutil" "net" "net/http" "time" - - log "github.com/sirupsen/logrus" - - "github.com/tencentyun/scf-go-lib/events" ) func Handler(ctx context.Context, reqOrigin events.APIGatewayRequest) (resp events.APIGatewayResponse) { var reqEvent = new(DefineEvent) + //b64dreqOrigin, _ := base64.StdEncoding.DecodeString(reqOrigin.Body) if err := json.Unmarshal([]byte(reqOrigin.Body), reqEvent); err != nil { return handlerErr(reqOrigin, err.Error()) } @@ -55,12 +54,18 @@ func forworld(reqevent *DefineEvent) (*RespEvent, error) { for k, v := range originreq.Header { req.Header.Set(k, v[0]) } + // EOF + req.Close = true tr := &http.Transport{ Dial: (&net.Dialer{ //LocalAddr: localAddr, Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).Dial, + //DialContext: (&net.Dialer{ + // Timeout: 30 * time.Second, + // KeepAlive: 30 * time.Second, + //}).DialContext, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := http.Client{Transport: tr, Timeout: 60 * time.Second} @@ -72,13 +77,25 @@ func forworld(reqevent *DefineEvent) (*RespEvent, error) { defer resp.Body.Close() byteresp, _ := ioutil.ReadAll(resp.Body) - respvent.Data = base64.StdEncoding.EncodeToString(byteresp) + byterespHeaders, _ := json.Marshal(resp.Header) + respHeaders := base64.StdEncoding.EncodeToString(byterespHeaders) + //响应headers + //{"Accept-Ranges":["bytes"], + //"Content-Length":["573"], + //"Content-Type":["text/html; charset=utf-8"], + //"Date":["Mon, 27 Jun 2022 02:56:52 GMT"], + //"Etag":["\"61419ca8-23d\""], + //"Last-Modified":["Wed, 15 Sep 2021 07:11:36 GMT"], + //"Server":["nginx/1.20.1"]} + respBoby := base64.StdEncoding.EncodeToString(byteresp) + respvent.Data = respHeaders + "^" + respBoby + //respvent.Data = base64.StdEncoding.EncodeToString(byteresp) respvent.Status = true return respvent, nil } -// handleErr 处理错误 +//handleErr 处理错误 func handlerErr(reqOrigin events.APIGatewayRequest, errString string) (resp events.APIGatewayResponse) { // log log.Printf("[出现错误] \n//err %v \n//req %v \n========== \n", errString, reqOrigin) diff --git a/pkg/scf/type.go b/pkg/scf/type.go index 312956e..a4fac75 100644 --- a/pkg/scf/type.go +++ b/pkg/scf/type.go @@ -10,5 +10,5 @@ type DefineEvent struct { type RespEvent struct { Status bool `json:"status"` // 请求是否正常 Error string `json:"error"` // 错误信息 - Data string `json:"data"` // HTTP 响应原始报文, base64 + Data string `json:"data"` // HTTP 响应原始头+报文, base64^base64 } diff --git a/pkg/viper/config.go b/pkg/viper/config.go new file mode 100644 index 0000000..a69e352 --- /dev/null +++ b/pkg/viper/config.go @@ -0,0 +1,35 @@ +package viper + +import ( + "github.com/chroblert/jlog" + "github.com/spf13/viper" +) + +func YamlConfig() (scfurl, proxyport string) { + viper.AddConfigPath(".") + viper.SetConfigName("config") + viper.SetConfigType("yaml") + viper.SetConfigPermissions(0644) + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + viper.Set("scfurl", "") + viper.Set("proxyport", "1080") + err := viper.SafeWriteConfig() + if err != nil { + jlog.Fatal(err) + } + } else { + jlog.Println("read config error") + } + jlog.Fatal(err) + } + scfurl = viper.GetString("scfurl") + proxyport = viper.GetString("proxyport") + if scfurl == "" { + jlog.Fatal("云函数地址为空") + } + if proxyport == "" { + jlog.Fatal("本地代理端口为空") + } + return scfurl, proxyport +} From 4aefae647c16e7381687170da0606cac39c19ccf Mon Sep 17 00:00:00 2001 From: y1n Date: Thu, 21 Jul 2022 14:30:18 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=9A=E5=9C=A8?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E5=A4=B1=E8=B4=A5=E5=90=8E=E4=B9=9F=E4=BC=9A?= =?UTF-8?q?=E5=B0=86=E7=9B=B8=E5=BA=94=E7=9A=84=E5=93=8D=E5=BA=94=E5=A4=B4?= =?UTF-8?q?=E5=92=8C=E5=86=85=E5=AE=B9=E5=91=88=E7=8E=B0=E5=87=BA=E6=9D=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/client.go | 6 ------ pkg/mitm/mitm.go | 1 + pkg/scf/handler.go | 27 +++++++++++++++++++++------ pkg/scf/scfhandler.go | 6 ++++++ 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/cmd/client.go b/cmd/client.go index df2b79b..0e41191 100644 --- a/cmd/client.go +++ b/cmd/client.go @@ -32,12 +32,6 @@ func init() { jlog.SetLogCount(30) jlog.IsIniCreateNewLog(true) jlog.SetVerbose(true) - //flag.StringVar(&clientPort, "p", "1080", "scf-proxy 客户端端口") - //flag.StringVar(&scf.ScfApiProxyUrl, "scfurl", "", "scf-proxy 服务端地址") - //flag.Parse() - //if scf.ScfApiProxyUrl == "" { - // panic("scf-proxy 服务端地址为空") - //} scf.ScfApiProxyUrl, clientPort = viper.YamlConfig() } diff --git a/pkg/mitm/mitm.go b/pkg/mitm/mitm.go index ea298f4..d4439f2 100644 --- a/pkg/mitm/mitm.go +++ b/pkg/mitm/mitm.go @@ -90,6 +90,7 @@ func (wrapper *HandlerWrapper) intercept(resp http.ResponseWriter, req *http.Req listener := &mitmListener{tlsConnIn} handler := http.HandlerFunc(func(resp2 http.ResponseWriter, req2 *http.Request) { + //req2.ProtoMajor req2.URL.Scheme = "https" req2.URL.Host = req2.Host req2.RequestURI = req2.URL.String() diff --git a/pkg/scf/handler.go b/pkg/scf/handler.go index a70ad24..971e1b1 100644 --- a/pkg/scf/handler.go +++ b/pkg/scf/handler.go @@ -23,6 +23,7 @@ func HandlerHttp(w http.ResponseWriter, r *http.Request) { return } + // 解决当url为/的问题 event := &DefineEvent{ URL: r.URL.String(), Content: base64.StdEncoding.EncodeToString(dumpReq), @@ -51,12 +52,17 @@ func HandlerHttp(w http.ResponseWriter, r *http.Request) { var respevent RespEvent if err := json.Unmarshal(bytersp, &respevent); err != nil { jlog.Error(err) - w.WriteHeader(http.StatusServiceUnavailable) + jlog.Error(string(bytersp)) + w.WriteHeader(resp.StatusCode) + w.Write(bytersp) + //w.WriteHeader(http.StatusServiceUnavailable) return } if resp.StatusCode > 0 && resp.StatusCode != 200 { jlog.Error(err) - w.WriteHeader(http.StatusServiceUnavailable) + w.WriteHeader(resp.StatusCode) + w.Write(bytersp) + //w.WriteHeader(http.StatusServiceUnavailable) return } //处理头+内容 @@ -66,10 +72,15 @@ func HandlerHttp(w http.ResponseWriter, r *http.Request) { //retByte, err := base64.StdEncoding.DecodeString(respevent.Data) if err != nil { jlog.Error(err) - w.WriteHeader(http.StatusServiceUnavailable) + w.WriteHeader(resp.StatusCode) + return + } + err1 := resp.Body.Close() + if err1 != nil { + jlog.Error(err1) + w.WriteHeader(resp.StatusCode) return } - resp.Body.Close() respHeadersMap := make(map[string][]string) err = json.Unmarshal(respHeaders, &respHeadersMap) for k, v := range respHeadersMap { @@ -77,10 +88,14 @@ func HandlerHttp(w http.ResponseWriter, r *http.Request) { for _, val := range v { s = append(s, val) } - //jlog.Printf("%s:%s\n", k, s[0]) w.Header().Set(k, s[0]) } - w.Write(respBody) + _, err2 := w.Write(respBody) + if err2 != nil { + jlog.Error(err2) + w.WriteHeader(resp.StatusCode) + return + } //w.Write(retByte) return diff --git a/pkg/scf/scfhandler.go b/pkg/scf/scfhandler.go index 70201e9..ef5c7fe 100644 --- a/pkg/scf/scfhandler.go +++ b/pkg/scf/scfhandler.go @@ -50,6 +50,12 @@ func forworld(reqevent *DefineEvent) (*RespEvent, error) { if err != nil { return nil, err } + //log.Println("url:", originreq.RequestURI) + //log.Println("url1:", originreq) + //log.Println("url2:", originreq.URL) + //if originreq.RequestURI == "/" { + // originreq.RequestURI = originreq.Proto + //} req, _ := http.NewRequest(originreq.Method, originreq.RequestURI, originreq.Body) for k, v := range originreq.Header { req.Header.Set(k, v[0]) From c19ecb730256b533fe791038b65c96a74b300d67 Mon Sep 17 00:00:00 2001 From: y1n Date: Thu, 21 Jul 2022 15:29:06 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=9A=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E8=84=9A=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 83ac694..66a53cb 100644 --- a/build.sh +++ b/build.sh @@ -3,5 +3,5 @@ work_path=$(cd `dirname $0`/../; pwd) echo $work_path -GOOS=linux GOARCH=amd64 go build -o main cmd/main.go +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -a -ldflags '-s -w' -gcflags="all=-trimpath=${PWD}" -asmflags="all=-trimpath=${PWD}" -o main cmd/main.go zip main.zip main \ No newline at end of file From 22e5beb2bfefd7be0443ff0bab1df370c60124c0 Mon Sep 17 00:00:00 2001 From: y1n Date: Thu, 21 Jul 2022 15:29:55 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=9A=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E6=97=B6=E5=8E=BB=E9=99=A4=E7=AC=A6=E5=8F=B7=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/build.sh b/build.sh index 66a53cb..acf2a94 100644 --- a/build.sh +++ b/build.sh @@ -1,4 +1,3 @@ - #!/bin/bash work_path=$(cd `dirname $0`/../; pwd) echo $work_path