From 199fd9b9fc3a574dcd78c9e8e59e35c1887cf5c4 Mon Sep 17 00:00:00 2001 From: orestonce Date: Tue, 21 Jun 2022 07:22:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=AE=BE=E7=BD=AE=E4=BB=A3?= =?UTF-8?q?=E7=90=86,=E5=8C=85=E6=8B=AChttp/socks5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- cmd/main.go | 1 + download.go | 5 +- go.mod | 4 +- go.sum | 16 +++++- m3u8d-qt/m3u8d.cpp | 110 ++++++++++++++++++++++------------------ m3u8d-qt/m3u8d.h | 5 ++ m3u8d-qt/mainwindow.cpp | 3 ++ m3u8d-qt/mainwindow.ui | 83 ++++++++++++++++++++---------- proxy.go | 101 ++++++++++++++++++++++++++++++++++++ 10 files changed, 248 insertions(+), 82 deletions(-) create mode 100644 proxy.go diff --git a/README.md b/README.md index 4b0b1af..3b11f52 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ * [x] windows、linux、mac都支持ffmpeg合并ts列表为mp4 * [x] 充分测试后,使用 https://github.com/yapingcat/gomedia 代替ffmpeg进行格式转换 * [x] 支持嵌套m3u8的url - * [ ] 支持设置代理 + * [x] 支持设置代理 * [ ] 支持从curl命令解析出需要的header、auth-basic、cookie等信息,正如 https://github.com/cxjava/m3u8-downloader 一样 ## 二次开发操作手册: * 如果只开发命令行版本, 则只需要修改*.go文件, 然后编译 cmd/main.go 即可 diff --git a/cmd/main.go b/cmd/main.go index e1b129b..60ff600 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -33,6 +33,7 @@ func init() { rootCmd.Flags().StringVarP(&gRunReq.SaveDir, "SaveDir", "d", "", "文件保存路径(默认为当前路径)") rootCmd.Flags().StringVarP(&gRunReq.FileName, "FileName", "f", "", "文件名") rootCmd.Flags().IntVarP(&gRunReq.SkipTsCountFromHead, "SkipTsCountFromHead", "", 0, "跳过前面几个ts") + rootCmd.Flags().StringVarP(&gRunReq.SetProxy, "SetProxy", "", "", "代理设置, http://127.0.0.1:8080, socks") } func main() { diff --git a/download.go b/download.go index 0ca496d..89f89ff 100644 --- a/download.go +++ b/download.go @@ -69,6 +69,7 @@ type RunDownload_Req struct { SaveDir string // "文件保存路径(默认为当前路径)" FileName string // 文件名 SkipTsCountFromHead int // 跳过前面几个ts + SetProxy string } type downloadEnv struct { @@ -246,12 +247,14 @@ var gOldEnv *downloadEnv var gOldEnvLocker sync.Mutex func RunDownload(req RunDownload_Req) (resp RunDownload_Resp) { + req.SetProxy = strings.ToLower(req.SetProxy) env := &downloadEnv{ client: &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: req.Insecure, }, + DialContext: newDialContext(req.SetProxy), }, Timeout: time.Second * 10, }, @@ -577,7 +580,7 @@ func (this *downloadEnv) doGetRequest(urlS string) (data []byte, err error) { } req = req.WithContext(this.ctx) req.Header = this.header - resp, err := http.DefaultClient.Do(req) + resp, err := this.client.Do(req) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index aff05d4..2fc48c2 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,13 @@ module m3u8d go 1.17 require ( + github.com/elazarl/goproxy v0.0.0-20220529153421-8ea89ba92021 github.com/orestonce/cdb v0.0.0-20220528005855-d187c22240e2 - github.com/orestonce/go2cpp v0.0.0-20220507123906-b66d3600c123 + github.com/orestonce/go2cpp v0.0.0-20220605110533-c77a8dd7fdfd github.com/orestonce/gopool v0.0.0-20220508090328-d7d56d45b171 github.com/spf13/cobra v1.4.0 github.com/yapingcat/gomedia v0.0.0-20220619163023-5a5544262ef6 + golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 ) require ( diff --git a/go.sum b/go.sum index 9b88a76..1933840 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,17 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/elazarl/goproxy v0.0.0-20220529153421-8ea89ba92021 h1:EbF0UihnxWRcIMOwoVtqnAylsqcjzqpSvMdjF2Ud4rA= +github.com/elazarl/goproxy v0.0.0-20220529153421-8ea89ba92021/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/orestonce/cdb v0.0.0-20220528005855-d187c22240e2 h1:AmGkuxSIOW0gA/jetY8d1DVV0cyQ08FMCxa1arkI6HQ= github.com/orestonce/cdb v0.0.0-20220528005855-d187c22240e2/go.mod h1:HMNNdA1LMQFJRwobtCzVevWcInFSo9rfs1fYeVYwU+c= -github.com/orestonce/go2cpp v0.0.0-20220507123906-b66d3600c123 h1:AyAKqO7kC68OKiZk9GV4bUG+xMe1VQCy2CPbS9hvdY4= -github.com/orestonce/go2cpp v0.0.0-20220507123906-b66d3600c123/go.mod h1:1fsOAZftk08/dOTRqlp6f/MVwaEKOhrnPUg0RtWiSdY= +github.com/orestonce/go2cpp v0.0.0-20220605110533-c77a8dd7fdfd h1:q4MMzJG2Zdh9OLSiQfhPqL0/GGQ+B+SZmYDjbOjGW7Y= +github.com/orestonce/go2cpp v0.0.0-20220605110533-c77a8dd7fdfd/go.mod h1:1fsOAZftk08/dOTRqlp6f/MVwaEKOhrnPUg0RtWiSdY= github.com/orestonce/gopool v0.0.0-20220508090328-d7d56d45b171 h1:NnOl6HTrhrlTT7aaAybVOtq+fEztGFMoQtegckgwLdk= github.com/orestonce/gopool v0.0.0-20220508090328-d7d56d45b171/go.mod h1:MCQUrAPiG9/PTjHJuGqWLasKbIaG6z62KO6kfp90byM= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= @@ -14,5 +19,12 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/yapingcat/gomedia v0.0.0-20220619163023-5a5544262ef6 h1:d9MplS5tlYkVzS20zFz47Hg82wvTnTk4Oafwo6j9Eb0= github.com/yapingcat/gomedia v0.0.0-20220619163023-5a5544262ef6/go.mod h1:WSZ59bidJOO40JSJmLqlkBJrjZCtjbKKkygEMfzY/kc= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/m3u8d-qt/m3u8d.cpp b/m3u8d-qt/m3u8d.cpp index abe945b..cd21b23 100644 --- a/m3u8d-qt/m3u8d.cpp +++ b/m3u8d-qt/m3u8d.cpp @@ -83,53 +83,63 @@ RunDownload_Resp RunDownload(RunDownload_Req in0){ std::string in; { { - uint32_t tmp19 = in0.M3u8Url.length(); - char tmp20[4]; - tmp20[0] = (uint32_t(tmp19) >> 24) & 0xFF; - tmp20[1] = (uint32_t(tmp19) >> 16) & 0xFF; - tmp20[2] = (uint32_t(tmp19) >> 8) & 0xFF; - tmp20[3] = (uint32_t(tmp19) >> 0) & 0xFF; - in.append(tmp20, 4); + uint32_t tmp22 = in0.M3u8Url.length(); + char tmp23[4]; + tmp23[0] = (uint32_t(tmp22) >> 24) & 0xFF; + tmp23[1] = (uint32_t(tmp22) >> 16) & 0xFF; + tmp23[2] = (uint32_t(tmp22) >> 8) & 0xFF; + tmp23[3] = (uint32_t(tmp22) >> 0) & 0xFF; + in.append(tmp23, 4); in.append(in0.M3u8Url); } { - uint32_t tmp21 = in0.HostType.length(); - char tmp22[4]; - tmp22[0] = (uint32_t(tmp21) >> 24) & 0xFF; - tmp22[1] = (uint32_t(tmp21) >> 16) & 0xFF; - tmp22[2] = (uint32_t(tmp21) >> 8) & 0xFF; - tmp22[3] = (uint32_t(tmp21) >> 0) & 0xFF; - in.append(tmp22, 4); + uint32_t tmp24 = in0.HostType.length(); + char tmp25[4]; + tmp25[0] = (uint32_t(tmp24) >> 24) & 0xFF; + tmp25[1] = (uint32_t(tmp24) >> 16) & 0xFF; + tmp25[2] = (uint32_t(tmp24) >> 8) & 0xFF; + tmp25[3] = (uint32_t(tmp24) >> 0) & 0xFF; + in.append(tmp25, 4); in.append(in0.HostType); } in.append((char*)(&in0.Insecure), 1); { - uint32_t tmp23 = in0.SaveDir.length(); - char tmp24[4]; - tmp24[0] = (uint32_t(tmp23) >> 24) & 0xFF; - tmp24[1] = (uint32_t(tmp23) >> 16) & 0xFF; - tmp24[2] = (uint32_t(tmp23) >> 8) & 0xFF; - tmp24[3] = (uint32_t(tmp23) >> 0) & 0xFF; - in.append(tmp24, 4); + uint32_t tmp26 = in0.SaveDir.length(); + char tmp27[4]; + tmp27[0] = (uint32_t(tmp26) >> 24) & 0xFF; + tmp27[1] = (uint32_t(tmp26) >> 16) & 0xFF; + tmp27[2] = (uint32_t(tmp26) >> 8) & 0xFF; + tmp27[3] = (uint32_t(tmp26) >> 0) & 0xFF; + in.append(tmp27, 4); in.append(in0.SaveDir); } { - uint32_t tmp25 = in0.FileName.length(); - char tmp26[4]; - tmp26[0] = (uint32_t(tmp25) >> 24) & 0xFF; - tmp26[1] = (uint32_t(tmp25) >> 16) & 0xFF; - tmp26[2] = (uint32_t(tmp25) >> 8) & 0xFF; - tmp26[3] = (uint32_t(tmp25) >> 0) & 0xFF; - in.append(tmp26, 4); + uint32_t tmp28 = in0.FileName.length(); + char tmp29[4]; + tmp29[0] = (uint32_t(tmp28) >> 24) & 0xFF; + tmp29[1] = (uint32_t(tmp28) >> 16) & 0xFF; + tmp29[2] = (uint32_t(tmp28) >> 8) & 0xFF; + tmp29[3] = (uint32_t(tmp28) >> 0) & 0xFF; + in.append(tmp29, 4); in.append(in0.FileName); } { - char tmp27[4]; - tmp27[0] = (uint32_t(in0.SkipTsCountFromHead) >> 24) & 0xFF; - tmp27[1] = (uint32_t(in0.SkipTsCountFromHead) >> 16) & 0xFF; - tmp27[2] = (uint32_t(in0.SkipTsCountFromHead) >> 8) & 0xFF; - tmp27[3] = (uint32_t(in0.SkipTsCountFromHead) >> 0) & 0xFF; - in.append(tmp27, 4); + char tmp30[4]; + tmp30[0] = (uint32_t(in0.SkipTsCountFromHead) >> 24) & 0xFF; + tmp30[1] = (uint32_t(in0.SkipTsCountFromHead) >> 16) & 0xFF; + tmp30[2] = (uint32_t(in0.SkipTsCountFromHead) >> 8) & 0xFF; + tmp30[3] = (uint32_t(in0.SkipTsCountFromHead) >> 0) & 0xFF; + in.append(tmp30, 4); + } + { + uint32_t tmp31 = in0.SetProxy.length(); + char tmp32[4]; + tmp32[0] = (uint32_t(tmp31) >> 24) & 0xFF; + tmp32[1] = (uint32_t(tmp31) >> 16) & 0xFF; + tmp32[2] = (uint32_t(tmp31) >> 8) & 0xFF; + tmp32[3] = (uint32_t(tmp31) >> 0) & 0xFF; + in.append(tmp32, 4); + in.append(in0.SetProxy); } } char *out = NULL; @@ -138,21 +148,6 @@ RunDownload_Resp RunDownload(RunDownload_Req in0){ RunDownload_Resp retValue; int outIdx = 0; { - { - uint32_t tmp28 = 0; - uint32_t tmp29 = uint32_t(uint8_t(out[outIdx+0]) << 24); - uint32_t tmp30 = uint32_t(uint8_t(out[outIdx+1]) << 16); - uint32_t tmp31 = uint32_t(uint8_t(out[outIdx+2]) << 8); - uint32_t tmp32 = uint32_t(uint8_t(out[outIdx+3]) << 0); - tmp28 = tmp29 | tmp30 | tmp31 | tmp32; - outIdx+=4; - retValue.ErrMsg = std::string(out+outIdx, out+outIdx+tmp28); - outIdx+=tmp28; - } - retValue.IsSkipped = (bool) out[outIdx]; - outIdx++; - retValue.IsCancel = (bool) out[outIdx]; - outIdx++; { uint32_t tmp33 = 0; uint32_t tmp34 = uint32_t(uint8_t(out[outIdx+0]) << 24); @@ -161,9 +156,24 @@ RunDownload_Resp RunDownload(RunDownload_Req in0){ uint32_t tmp37 = uint32_t(uint8_t(out[outIdx+3]) << 0); tmp33 = tmp34 | tmp35 | tmp36 | tmp37; outIdx+=4; - retValue.SaveFileTo = std::string(out+outIdx, out+outIdx+tmp33); + retValue.ErrMsg = std::string(out+outIdx, out+outIdx+tmp33); outIdx+=tmp33; } + retValue.IsSkipped = (bool) out[outIdx]; + outIdx++; + retValue.IsCancel = (bool) out[outIdx]; + outIdx++; + { + uint32_t tmp38 = 0; + uint32_t tmp39 = uint32_t(uint8_t(out[outIdx+0]) << 24); + uint32_t tmp40 = uint32_t(uint8_t(out[outIdx+1]) << 16); + uint32_t tmp41 = uint32_t(uint8_t(out[outIdx+2]) << 8); + uint32_t tmp42 = uint32_t(uint8_t(out[outIdx+3]) << 0); + tmp38 = tmp39 | tmp40 | tmp41 | tmp42; + outIdx+=4; + retValue.SaveFileTo = std::string(out+outIdx, out+outIdx+tmp38); + outIdx+=tmp38; + } } if (out != NULL) { free(out); diff --git a/m3u8d-qt/m3u8d.h b/m3u8d-qt/m3u8d.h index 082b91b..ebba006 100644 --- a/m3u8d-qt/m3u8d.h +++ b/m3u8d-qt/m3u8d.h @@ -3,6 +3,7 @@ #include #include #include +#include //Qt Creator 需要在xxx.pro 内部增加静态库的链接声明 //LIBS += -L$$PWD -lm3u8d-impl @@ -13,18 +14,22 @@ struct RunDownload_Req{ std::string SaveDir; std::string FileName; int32_t SkipTsCountFromHead; + std::string SetProxy; + RunDownload_Req(): Insecure(false),SkipTsCountFromHead(0){} }; struct RunDownload_Resp{ std::string ErrMsg; bool IsSkipped; bool IsCancel; std::string SaveFileTo; + RunDownload_Resp(): IsSkipped(false),IsCancel(false){} }; RunDownload_Resp RunDownload(RunDownload_Req in0); void CloseOldEnv(); struct GetProgress_Resp{ int32_t Percent; std::string Title; + GetProgress_Resp(): Percent(0){} }; GetProgress_Resp GetProgress(); std::string GetWd(); diff --git a/m3u8d-qt/mainwindow.cpp b/m3u8d-qt/mainwindow.cpp index ed8f2b5..5c1e4f8 100644 --- a/m3u8d-qt/mainwindow.cpp +++ b/m3u8d-qt/mainwindow.cpp @@ -35,6 +35,7 @@ void MainWindow::on_pushButton_RunDownload_clicked() ui->pushButton_RunDownload->setEnabled(false); ui->checkBox_Insecure->setEnabled(false); ui->progressBar->setValue(0); + ui->lineEdit_SetProxy->setEnabled(false); ui->pushButton_StopDownload->setEnabled(true); m_syncUi.AddRunFnOn_OtherThread([this](){ @@ -62,6 +63,7 @@ void MainWindow::on_pushButton_RunDownload_clicked() req.SaveDir = ui->lineEdit_SaveDir->text().toStdString(); req.FileName = ui->lineEdit_FileName->text().toStdString(); req.SkipTsCountFromHead = ui->lineEdit_SkipTsCountFromHead->text().toInt(); + req.SetProxy = ui->lineEdit_SetProxy->text().toStdString(); m_syncUi.AddRunFnOn_OtherThread([req, this](){ RunDownload_Resp resp = RunDownload(req); @@ -75,6 +77,7 @@ void MainWindow::on_pushButton_RunDownload_clicked() ui->pushButton_RunDownload->setEnabled(true); ui->checkBox_Insecure->setEnabled(true); ui->pushButton_RunDownload->setText("开始下载"); + ui->lineEdit_SetProxy->setEnabled(true); ui->pushButton_StopDownload->setEnabled(false); if (resp.IsCancel) { return; diff --git a/m3u8d-qt/mainwindow.ui b/m3u8d-qt/mainwindow.ui index 6da1585..2c38bf0 100644 --- a/m3u8d-qt/mainwindow.ui +++ b/m3u8d-qt/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 747 - 247 + 802 + 260 @@ -18,21 +18,21 @@ - + - HostType + 代理设置 - - + + - ... + 跳过前面几个TS - - + + @@ -41,17 +41,46 @@ - + + + + + + + + + + + + + + 保存位置 + + + + all - - + + + + + 40 + 40 + + + + + 40 + 20 + + - 保存位置 + ... @@ -62,17 +91,7 @@ - - - - - - - - - - - + @@ -86,10 +105,20 @@ - - + + - 跳过前面几个TS + HostType + + + + + + + + + + http://127.0.0.1:8080 socks5://127.0.0.1:1089 diff --git a/proxy.go b/proxy.go new file mode 100644 index 0000000..be64e06 --- /dev/null +++ b/proxy.go @@ -0,0 +1,101 @@ +package m3u8d + +import ( + "bufio" + "bytes" + "context" + "crypto/tls" + "encoding/base64" + "errors" + "fmt" + "golang.org/x/net/proxy" + "net" + "net/http" + "net/url" + "strings" +) + +func newDialContext(setProxy string) func(ctx context.Context, network, addr string) (net.Conn, error) { + return func(ctx context.Context, network, addr string) (net.Conn, error) { + if setProxy == "" { + return (&net.Dialer{}).DialContext(ctx, network, addr) + } else if strings.HasPrefix(setProxy, "http") { + return proxyByHttp(setProxy, ctx, addr) + } else { // socks5 + urlObj, err := url.Parse(setProxy) + if err != nil { + return nil, err + } + dialer, err := proxy.FromURL(urlObj, nil) + if err != nil { + return nil, err + } + return dialer.Dial(network, addr) + } + } + +} + +func proxyByHttp(setProxy string, ctx context.Context, to string) (net.Conn, error) { + // https://github.com/aidenesco/connect/blob/master/proxy.go + urlObj, err := url.Parse(setProxy) + if err != nil { + return nil, err + } + var tConn net.Conn + if strings.HasPrefix(setProxy, "https://") { + host := urlObj.Host + _, _, err = net.SplitHostPort(host) + if err != nil { + host = host + ":443" + } + tConn, err = (&tls.Dialer{}).DialContext(ctx, "tcp", host) + } else { + host := urlObj.Host + _, _, err = net.SplitHostPort(host) + if err != nil { + host = host + ":80" + } + tConn, err = (&net.Dialer{}).DialContext(ctx, "tcp", host) + } + + if err != nil { + return nil, err + } + defer func() { + if err != nil { + tConn.Close() + } + }() + + buf0 := bytes.NewBuffer(nil) + buf0.WriteString(`CONNECT ` + to + ` HTTP/1.1` + "\r\n") + + if u := urlObj.User; u != nil { + username := u.Username() + password, _ := u.Password() + buf0.WriteString("Proxy-Authorization: Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) + "\r\n") + } + buf0.WriteString("\r\n") + _, err = tConn.Write(buf0.Bytes()) + if err != nil { + return nil, err + } + + bufr := bufio.NewReader(tConn) + + var response *http.Response + response, err = http.ReadResponse(bufr, nil) + if err != nil { + return nil, err + } + + switch response.StatusCode { + case http.StatusOK: + return tConn, nil + case http.StatusProxyAuthRequired: + return nil, errors.New("connect: invalid or missing \"Proxy-Authorization\" header") + default: + return nil, fmt.Errorf("connect: unexpected CONNECT response status \"%s\" (expect 200 OK)", response.Status) + } +}