11package tools
22
33import (
4+ "compress/gzip"
5+ "fmt"
6+ "io"
7+ "io/ioutil"
48 "log"
59 "net/http"
610 "net/http/httputil"
711 "net/url"
12+ "strconv"
813 "strings"
914)
1015
16+ var originalUrlResolver = make (map [string ]* url.URL )
17+
1118// ProxyRequestHandler intercepts requests to CodeArtifact and add the Authorization header + correct Host header
1219func ProxyRequestHandler (p * httputil.ReverseProxy ) func (http.ResponseWriter , * http.Request ) {
1320 return func (w http.ResponseWriter , r * http.Request ) {
21+ // Store the original host header for each request
22+ originalUrlResolver [r .RemoteAddr ] = r .URL
23+ originalUrlResolver [r .RemoteAddr ].Host = r .Host
24+ originalUrlResolver [r .RemoteAddr ].Scheme = r .URL .Scheme
25+
26+ if r .Header .Get ("X-Forwarded-Proto" ) == "https" {
27+ originalUrlResolver [r .RemoteAddr ].Scheme = "https"
28+ } else {
29+ originalUrlResolver [r .RemoteAddr ].Scheme = "http"
30+ }
31+
1432 // Override the Host header with the CodeArtifact Host
1533 u , _ := url .Parse (CodeArtifactAuthInfo .Url )
1634 r .Host = u .Host
@@ -28,10 +46,65 @@ func ProxyRequestHandler(p *httputil.ReverseProxy) func(http.ResponseWriter, *ht
2846
2947func ProxyResponseHandler () func (* http.Response ) error {
3048 return func (r * http.Response ) error {
49+ log .Printf ("Received response from %s" , r .Request .URL .String ())
3150 log .Printf ("RES: %s \" %s\" %d \" %s\" \" %s\" " , r .Request .RemoteAddr , r .Request .Method , r .StatusCode , r .Request .RequestURI , r .Request .UserAgent ())
3251
52+ contentType := r .Header .Get ("Content-Type" )
53+
54+ originalUrl := originalUrlResolver [r .Request .RemoteAddr ]
55+ delete (originalUrlResolver , r .Request .RemoteAddr )
56+
57+ u , _ := url .Parse (CodeArtifactAuthInfo .Url )
58+ hostname := u .Host + ":443"
59+
60+ // Rewrite the 301 to point from CodeArtifact URL to the proxy instead..
61+ if r .StatusCode == 301 || r .StatusCode == 302 {
62+ location , _ := r .Location ()
63+
64+ location .Host = originalUrl .Host
65+ location .Scheme = originalUrl .Scheme
66+ location .Path = strings .Replace (location .Path , u .Path , "" , 1 )
67+
68+ r .Header .Set ("Location" , location .String ())
69+ }
70+
71+ // Do some quick fixes to the HTTP response for NPM install requests
72+ // TODO: Get this actually working, it looks like the JSON responses provide the correct URLs via CURL, but not when using npm against it.
73+ if strings .HasPrefix (r .Request .UserAgent (), "npm" ) {
74+
75+ // Respond to only requests that respond with JSON
76+ // There might eventually be additional headers i don't know about?
77+ if ! strings .Contains (contentType , "application/json" ) && ! strings .Contains (contentType , "application/vnd.npm.install-v1+json" ) {
78+ return nil
79+ }
80+
81+ var body io.ReadCloser
82+
83+ if r .Header .Get ("Content-Encoding" ) == "gzip" {
84+ body , _ = gzip .NewReader (r .Body )
85+ r .Header .Del ("Content-Encoding" )
86+ } else {
87+ body = r .Body
88+ }
89+
90+ // replace any instances of the CodeArtifact URL with the local URL
91+ oldContentResponse , _ := ioutil .ReadAll (body )
92+ oldContentResponseStr := string (oldContentResponse )
93+
94+ resolvedHostname := strings .Replace (CodeArtifactAuthInfo .Url , u .Host , hostname , - 1 )
95+ newUrl := fmt .Sprintf ("%s://%s/" , originalUrl .Scheme , originalUrl .Host )
96+
97+ newResponseContent := strings .Replace (oldContentResponseStr , resolvedHostname , newUrl , - 1 )
98+ newResponseContent = strings .Replace (newResponseContent , CodeArtifactAuthInfo .Url , newUrl , - 1 )
99+
100+ r .Body = ioutil .NopCloser (strings .NewReader (newResponseContent ))
101+ r .ContentLength = int64 (len (newResponseContent ))
102+ r .Header .Set ("Content-Length" , strconv .Itoa (len (newResponseContent )))
103+ }
104+
33105 return nil
34106 }
107+
35108}
36109
37110// ProxyInit initialises the CodeArtifact proxy and starts the HTTP listener
0 commit comments