@@ -618,11 +618,6 @@ func (t *Transport) connectMethodForRequest(treq *transportRequest) (cm connectM
618
618
if port := cm .proxyURL .Port (); ! validPort (port ) {
619
619
return cm , fmt .Errorf ("invalid proxy URL port %q" , port )
620
620
}
621
- switch cm .proxyURL .Scheme {
622
- case "http" , "socks5" :
623
- default :
624
- return cm , fmt .Errorf ("invalid proxy URL scheme %q" , cm .proxyURL .Scheme )
625
- }
626
621
}
627
622
}
628
623
return cm , err
@@ -1021,6 +1016,69 @@ func (d oneConnDialer) Dial(network, addr string) (net.Conn, error) {
1021
1016
}
1022
1017
}
1023
1018
1019
+ // The connect method and the transport can both specify a TLS
1020
+ // Host name. The transport's name takes precedence if present.
1021
+ func chooseTLSHost (cm connectMethod , t * Transport ) string {
1022
+ tlsHost := ""
1023
+ if t .TLSClientConfig != nil {
1024
+ tlsHost = t .TLSClientConfig .ServerName
1025
+ }
1026
+ if tlsHost == "" {
1027
+ tlsHost = cm .tlsHost ()
1028
+ }
1029
+ return tlsHost
1030
+ }
1031
+
1032
+ // Add TLS to a persistent connection, i.e. negotiate a TLS session. If pconn is already a TLS
1033
+ // tunnel, this function establishes a nested TLS session inside the encrypted channel.
1034
+ // The remote endpoint's name may be overridden by TLSClientConfig.ServerName.
1035
+ func (pconn * persistConn ) addTLS (name string , trace * httptrace.ClientTrace ) error {
1036
+ // Initiate TLS and check remote host name against certificate.
1037
+ cfg := cloneTLSConfig (pconn .t .TLSClientConfig )
1038
+ if cfg .ServerName == "" {
1039
+ cfg .ServerName = name
1040
+ }
1041
+ plainConn := pconn .conn
1042
+ tlsConn := tls .Client (plainConn , cfg )
1043
+ errc := make (chan error , 2 )
1044
+ var timer * time.Timer // for canceling TLS handshake
1045
+ if d := pconn .t .TLSHandshakeTimeout ; d != 0 {
1046
+ timer = time .AfterFunc (d , func () {
1047
+ errc <- tlsHandshakeTimeoutError {}
1048
+ })
1049
+ }
1050
+ go func () {
1051
+ if trace != nil && trace .TLSHandshakeStart != nil {
1052
+ trace .TLSHandshakeStart ()
1053
+ }
1054
+ err := tlsConn .Handshake ()
1055
+ if timer != nil {
1056
+ timer .Stop ()
1057
+ }
1058
+ errc <- err
1059
+ }()
1060
+ if err := <- errc ; err != nil {
1061
+ plainConn .Close ()
1062
+ if trace != nil && trace .TLSHandshakeDone != nil {
1063
+ trace .TLSHandshakeDone (tls.ConnectionState {}, err )
1064
+ }
1065
+ return err
1066
+ }
1067
+ if ! cfg .InsecureSkipVerify {
1068
+ if err := tlsConn .VerifyHostname (cfg .ServerName ); err != nil {
1069
+ plainConn .Close ()
1070
+ return err
1071
+ }
1072
+ }
1073
+ cs := tlsConn .ConnectionState ()
1074
+ if trace != nil && trace .TLSHandshakeDone != nil {
1075
+ trace .TLSHandshakeDone (cs , nil )
1076
+ }
1077
+ pconn .tlsState = & cs
1078
+ pconn .conn = tlsConn
1079
+ return nil
1080
+ }
1081
+
1024
1082
func (t * Transport ) dialConn (ctx context.Context , cm connectMethod ) (* persistConn , error ) {
1025
1083
pconn := & persistConn {
1026
1084
t : t ,
@@ -1032,15 +1090,21 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistCon
1032
1090
writeLoopDone : make (chan struct {}),
1033
1091
}
1034
1092
trace := httptrace .ContextClientTrace (ctx )
1035
- tlsDial := t .DialTLS != nil && cm .targetScheme == "https" && cm .proxyURL == nil
1036
- if tlsDial {
1093
+ wrapErr := func (err error ) error {
1094
+ if cm .proxyURL != nil {
1095
+ // Return a typed error, per Issue 16997
1096
+ return & net.OpError {Op : "proxyconnect" , Net : "tcp" , Err : err }
1097
+ }
1098
+ return err
1099
+ }
1100
+ if cm .scheme () == "https" && t .DialTLS != nil {
1037
1101
var err error
1038
1102
pconn .conn , err = t .DialTLS ("tcp" , cm .addr ())
1039
1103
if err != nil {
1040
- return nil , err
1104
+ return nil , wrapErr ( err )
1041
1105
}
1042
1106
if pconn .conn == nil {
1043
- return nil , errors .New ("net/http: Transport.DialTLS returned (nil, nil)" )
1107
+ return nil , wrapErr ( errors .New ("net/http: Transport.DialTLS returned (nil, nil)" ) )
1044
1108
}
1045
1109
if tc , ok := pconn .conn .(* tls.Conn ); ok {
1046
1110
// Handshake here, in case DialTLS didn't. TLSNextProto below
@@ -1064,13 +1128,18 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistCon
1064
1128
} else {
1065
1129
conn , err := t .dial (ctx , "tcp" , cm .addr ())
1066
1130
if err != nil {
1067
- if cm .proxyURL != nil {
1068
- // Return a typed error, per Issue 16997:
1069
- err = & net.OpError {Op : "proxyconnect" , Net : "tcp" , Err : err }
1070
- }
1071
- return nil , err
1131
+ return nil , wrapErr (err )
1072
1132
}
1073
1133
pconn .conn = conn
1134
+ if cm .scheme () == "https" {
1135
+ var firstTLSHost string
1136
+ if firstTLSHost , _ , err = net .SplitHostPort (cm .addr ()); err != nil {
1137
+ return nil , wrapErr (err )
1138
+ }
1139
+ if err = pconn .addTLS (firstTLSHost , trace ); err != nil {
1140
+ return nil , wrapErr (err )
1141
+ }
1142
+ }
1074
1143
}
1075
1144
1076
1145
// Proxy setup.
@@ -1134,50 +1203,10 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (*persistCon
1134
1203
}
1135
1204
}
1136
1205
1137
- if cm .targetScheme == "https" && ! tlsDial {
1138
- // Initiate TLS and check remote host name against certificate.
1139
- cfg := cloneTLSConfig (t .TLSClientConfig )
1140
- if cfg .ServerName == "" {
1141
- cfg .ServerName = cm .tlsHost ()
1142
- }
1143
- plainConn := pconn .conn
1144
- tlsConn := tls .Client (plainConn , cfg )
1145
- errc := make (chan error , 2 )
1146
- var timer * time.Timer // for canceling TLS handshake
1147
- if d := t .TLSHandshakeTimeout ; d != 0 {
1148
- timer = time .AfterFunc (d , func () {
1149
- errc <- tlsHandshakeTimeoutError {}
1150
- })
1151
- }
1152
- go func () {
1153
- if trace != nil && trace .TLSHandshakeStart != nil {
1154
- trace .TLSHandshakeStart ()
1155
- }
1156
- err := tlsConn .Handshake ()
1157
- if timer != nil {
1158
- timer .Stop ()
1159
- }
1160
- errc <- err
1161
- }()
1162
- if err := <- errc ; err != nil {
1163
- plainConn .Close ()
1164
- if trace != nil && trace .TLSHandshakeDone != nil {
1165
- trace .TLSHandshakeDone (tls.ConnectionState {}, err )
1166
- }
1206
+ if cm .proxyURL != nil && cm .targetScheme == "https" {
1207
+ if err := pconn .addTLS (cm .tlsHost (), trace ); err != nil {
1167
1208
return nil , err
1168
1209
}
1169
- if ! cfg .InsecureSkipVerify {
1170
- if err := tlsConn .VerifyHostname (cfg .ServerName ); err != nil {
1171
- plainConn .Close ()
1172
- return nil , err
1173
- }
1174
- }
1175
- cs := tlsConn .ConnectionState ()
1176
- if trace != nil && trace .TLSHandshakeDone != nil {
1177
- trace .TLSHandshakeDone (cs , nil )
1178
- }
1179
- pconn .tlsState = & cs
1180
- pconn .conn = tlsConn
1181
1210
}
1182
1211
1183
1212
if s := pconn .tlsState ; s != nil && s .NegotiatedProtocolIsMutual && s .NegotiatedProtocol != "" {
@@ -1279,21 +1308,24 @@ func useProxy(addr string) bool {
1279
1308
// http://proxy.com|http http to proxy, http to anywhere after that
1280
1309
// socks5://proxy.com|http|foo.com socks5 to proxy, then http to foo.com
1281
1310
// socks5://proxy.com|https|foo.com socks5 to proxy, then https to foo.com
1282
- //
1283
- // Note: no support to https to the proxy yet.
1311
+ // https://proxy.com|https|foo.com https to proxy, then CONNECT to foo.com
1312
+ // https://proxy.com|http https to proxy, http to anywhere after that
1284
1313
//
1285
1314
type connectMethod struct {
1286
1315
proxyURL * url.URL // nil for no proxy, else full proxy URL
1287
1316
targetScheme string // "http" or "https"
1288
- targetAddr string // Not used if http proxy + http targetScheme (4th example in table)
1317
+ // If proxyURL specifies an http or https proxy, and targetScheme is http (not https),
1318
+ // then targetAddr is not included in the connect method key, because the socket can
1319
+ // be reused for different targetAddr values.
1320
+ targetAddr string
1289
1321
}
1290
1322
1291
1323
func (cm * connectMethod ) key () connectMethodKey {
1292
1324
proxyStr := ""
1293
1325
targetAddr := cm .targetAddr
1294
1326
if cm .proxyURL != nil {
1295
1327
proxyStr = cm .proxyURL .String ()
1296
- if strings . HasPrefix (cm .proxyURL .Scheme , "http" ) && cm .targetScheme == "http" {
1328
+ if (cm .proxyURL .Scheme == "http" || cm . proxyURL . Scheme == "https " ) && cm .targetScheme == "http" {
1297
1329
targetAddr = ""
1298
1330
}
1299
1331
}
@@ -1304,6 +1336,14 @@ func (cm *connectMethod) key() connectMethodKey {
1304
1336
}
1305
1337
}
1306
1338
1339
+ // scheme returns the first hop scheme: http, https, or socks5
1340
+ func (cm * connectMethod ) scheme () string {
1341
+ if cm .proxyURL != nil {
1342
+ return cm .proxyURL .Scheme
1343
+ }
1344
+ return cm .targetScheme
1345
+ }
1346
+
1307
1347
// addr returns the first hop "host:port" to which we need to TCP connect.
1308
1348
func (cm * connectMethod ) addr () string {
1309
1349
if cm .proxyURL != nil {
0 commit comments