@@ -12,9 +12,20 @@ import (
1212 "github.com/go-rod/rod/lib/proto"
1313 "github.com/pkg/errors"
1414 fileutil "github.com/projectdiscovery/utils/file"
15+ mapsutil "github.com/projectdiscovery/utils/maps"
1516 osutils "github.com/projectdiscovery/utils/os"
17+ sliceutil "github.com/projectdiscovery/utils/slice"
18+ stringsutil "github.com/projectdiscovery/utils/strings"
1619)
1720
21+ type NetworkRequest struct {
22+ RequestID string
23+ URL string
24+ Method string
25+ StatusCode int
26+ ErrorType string
27+ }
28+
1829// MustDisableSandbox determines if the current os and user needs sandbox mode disabled
1930func MustDisableSandbox () bool {
2031 // linux with root user needs "--no-sandbox" option
@@ -99,12 +110,62 @@ func NewBrowser(proxy string, useLocal bool, optionalArgs map[string]string) (*B
99110 return engine , nil
100111}
101112
102- func (b * Browser ) ScreenshotWithBody (url string , timeout time.Duration , idle time.Duration , headers []string , fullPage bool ) ([]byte , string , error ) {
113+ func (b * Browser ) ScreenshotWithBody (url string , timeout time.Duration , idle time.Duration , headers []string , fullPage bool ) ([]byte , string , [] NetworkRequest , error ) {
103114 page , err := b .engine .Page (proto.TargetCreateTarget {})
104115 if err != nil {
105- return nil , "" , err
116+ return nil , "" , [] NetworkRequest {}, err
106117 }
107118
119+ // Enable network
120+ page .EnableDomain (proto.NetworkEnable {})
121+
122+ networkRequests := sliceutil .NewSyncSlice [NetworkRequest ]()
123+ requestsMap := mapsutil .NewSyncLockMap [string , * NetworkRequest ]()
124+
125+ // Intercept outbound requests
126+ go page .EachEvent (func (e * proto.NetworkRequestWillBeSent ) {
127+ if ! stringsutil .HasPrefixAnyI (e .Request .URL , "http://" , "https://" ) {
128+ return
129+ }
130+ req := & NetworkRequest {
131+ RequestID : string (e .RequestID ),
132+ URL : e .Request .URL ,
133+ Method : e .Request .Method ,
134+ StatusCode : - 1 ,
135+ ErrorType : "QUIT_BEFORE_RESOURCE_LOADING_END" ,
136+ }
137+ _ = requestsMap .Set (string (e .RequestID ), req )
138+ })()
139+ // Intercept inbound responses
140+ go page .EachEvent (func (e * proto.NetworkResponseReceived ) {
141+ if requestsMap .Has (string (e .RequestID )) {
142+ req , _ := requestsMap .Get (string (e .RequestID ))
143+ req .StatusCode = e .Response .Status
144+ }
145+ })()
146+ // Intercept network end requests
147+ go page .EachEvent (func (e * proto.NetworkLoadingFinished ) {
148+ if requestsMap .Has (string (e .RequestID )) {
149+ req , _ := requestsMap .Get (string (e .RequestID ))
150+ if req .StatusCode > 0 {
151+ req .ErrorType = ""
152+ }
153+ networkRequests .Append (* req )
154+ }
155+ })()
156+ // Intercept failed request
157+ go page .EachEvent (func (e * proto.NetworkLoadingFailed ) {
158+ if requestsMap .Has (string (e .RequestID )) {
159+ req , _ := requestsMap .Get (string (e .RequestID ))
160+ req .StatusCode = 0 // mark to zero
161+ req .ErrorType = getSimpleErrorType (e .ErrorText , string (e .Type ), string (e .BlockedReason ))
162+ if stringsutil .HasPrefixAnyI (req .URL , "http://" , "https://" ) {
163+ networkRequests .Append (* req )
164+ }
165+ }
166+ })()
167+
168+ // Handle any popup dialogs
108169 go page .EachEvent (func (e * proto.PageJavascriptDialogOpening ) {
109170 _ = proto.PageHandleJavaScriptDialog {
110171 Accept : true ,
@@ -128,31 +189,82 @@ func (b *Browser) ScreenshotWithBody(url string, timeout time.Duration, idle tim
128189 }()
129190
130191 if err := page .Navigate (url ); err != nil {
131- return nil , "" , err
192+ return nil , "" , networkRequests . Slice , err
132193 }
133194
134195 page .Timeout (5 * time .Second ).WaitNavigation (proto .PageLifecycleEventNameFirstMeaningfulPaint )()
135196
136197 if err := page .WaitLoad (); err != nil {
137- return nil , "" , err
198+ return nil , "" , networkRequests . Slice , err
138199 }
139200 _ = page .WaitIdle (idle )
140201
141202 screenshot , err := page .Screenshot (fullPage , & proto.PageCaptureScreenshot {})
142203 if err != nil {
143- return nil , "" , err
204+ return nil , "" , networkRequests . Slice , err
144205 }
145206
146207 body , err := page .HTML ()
147208 if err != nil {
148- return screenshot , "" , err
209+ return screenshot , "" , networkRequests . Slice , err
149210 }
150211
151- return screenshot , body , nil
212+ return screenshot , body , networkRequests . Slice , nil
152213}
153214
154215func (b * Browser ) Close () {
155216 _ = b .engine .Close ()
156217 _ = os .RemoveAll (b .tempDir )
157218 // processutil.CloseProcesses(processutil.IsChromeProcess, b.pids)
158219}
220+ func getSimpleErrorType (errorText , errorType , blockedReason string ) string {
221+ switch blockedReason {
222+ case "csp" :
223+ return "CSP_BLOCKED"
224+ case "mixed-content" :
225+ return "MIXED_CONTENT"
226+ case "origin" :
227+ return "CORS_BLOCKED"
228+ case "subresource-filter" :
229+ return "AD_BLOCKED"
230+ }
231+ switch {
232+ case strings .Contains (errorText , "net::ERR_NAME_NOT_RESOLVED" ):
233+ return "DNS_ERROR"
234+ case strings .Contains (errorText , "net::ERR_CONNECTION_REFUSED" ):
235+ return "CONNECTION_REFUSED"
236+ case strings .Contains (errorText , "net::ERR_CONNECTION_TIMED_OUT" ):
237+ return "TIMEOUT"
238+ case strings .Contains (errorText , "net::ERR_CERT_" ):
239+ return "SSL_ERROR"
240+ case strings .Contains (errorText , "net::ERR_BLOCKED_BY_CLIENT" ):
241+ return "CLIENT_BLOCKED"
242+ case strings .Contains (errorText , "net::ERR_EMPTY_RESPONSE" ):
243+ return "EMPTY_RESPONSE"
244+ }
245+ switch errorType {
246+ case "Failed" :
247+ return "NETWORK_FAILED"
248+ case "Aborted" :
249+ return "ABORTED"
250+ case "TimedOut" :
251+ return "TIMEOUT"
252+ case "AccessDenied" :
253+ return "ACCESS_DENIED"
254+ case "ConnectionClosed" :
255+ return "CONNECTION_CLOSED"
256+ case "ConnectionReset" :
257+ return "CONNECTION_RESET"
258+ case "ConnectionRefused" :
259+ return "CONNECTION_REFUSED"
260+ case "NameNotResolved" :
261+ return "DNS_ERROR"
262+ case "BlockedByClient" :
263+ return "CLIENT_BLOCKED"
264+ }
265+ // Fallback
266+ if errorText != "" {
267+ return "OTHER_ERROR"
268+ }
269+ return "UNKNOWN"
270+ }
0 commit comments