package qna import ( "context" "errors" "fmt" "net" "net/url" ) type Question struct { Domain string // domain name to visit via HTTPS } type Answer struct { Domain string // domain name of the visited HTTPS site HTTP string // value set in the Onion-Location HTTP header (if any) HTML string // value set in the Onion-Location HTML attribute (if any) ReqErr error // nil if HTTP GET request could be constructed DoErr error // nil if HTTP GET request could be executed } func (a Answer) String() string { if a.ReqErr != nil { return fmt.Sprintf("%s: %v", a.ReqErr) } if a.DoErr != nil { return fmt.Sprintf("%s: %v", a.DoErr) } if a.HTTP == "" && a.HTML == "" { return fmt.Sprintf("%s: connected but found no Onion-Location") } return fmt.Sprintf("%s header=%s attribute=%s", a.Domain, a.HTTP, a.HTML) } type Progress struct { NumReqErr int // error before sending requests NumDoErr int // error while sending request NumOK int // got response NumOnion int // found onion in response // More details about deadline errors while sending request NumDeadline int // More details about DNS errors while sending request NumDNSNotFound int NumDNSTimeout int NumDNSOther int } func (p Progress) String() string { str := fmt.Sprintf(" Processed: %d\n", p.NumAnswer()) str += fmt.Sprintf(" Success: %d (Onion-Location:%d)\n", p.NumOK, p.NumOnion) str += fmt.Sprintf(" Failure: %d (Req:%d Do:%d)\n", p.NumError(), p.NumReqErr, p.NumDoErr) str += fmt.Sprintf(" CTX: %d (Deadline exceeded)\n", p.NumDeadline) str += fmt.Sprintf(" DNS: %d (NotFound:%d Timeout:%d Other:%d)", p.NumDNSErr(), p.NumDNSNotFound, p.NumDNSTimeout, p.NumDNSOther) return str } func (p *Progress) NumAnswer() int { return p.NumReqErr + p.NumDoErr + p.NumOK } func (p *Progress) NumError() int { return p.NumReqErr + p.NumDoErr } func (p *Progress) NumDNSErr() int { return p.NumDNSNotFound + p.NumDNSTimeout + p.NumDNSOther } func (p *Progress) AddAnswer(a Answer) { if a.ReqErr != nil { p.NumReqErr += 1 return } if a.DoErr != nil { p.NumDoErr += 1 if isDeadlineError(a.DoErr) { p.NumDeadline += 1 } if err := dnsError(a.DoErr); err != nil { if err.IsTimeout { p.NumDNSTimeout += 1 } else if err.IsNotFound { p.NumDNSNotFound += 1 } else { p.NumDNSOther += 1 } } return } p.NumOK += 1 if a.HTTP != "" || a.HTML != "" { p.NumOnion += 1 } } func dnsError(err error) *net.DNSError { if err == nil { return nil } urlErr, ok := err.(*url.Error) if !ok { return nil } err = urlErr.Err opErr, ok := err.(*net.OpError) if !ok { return nil } err = opErr.Err dnsErr, ok := err.(*net.DNSError) if !ok { return nil } return dnsErr } func isDeadlineError(err error) bool { urlErr, ok := err.(*url.Error) return ok && errors.Is(urlErr.Err, context.DeadlineExceeded) } func TrimWildcard(san string) string { if len(san) >= 2 && san[:2] == "*." { return san[2:] } return san }