Projects STRLCPY csprecon Commits 315e78c6
🤬
  • ■ ■ ■ ■ ■
    .golangci.yml
    skipped 24 lines
    25 25   - gosimple
    26 26   - govet
    27 27   - ineffassign
    28  - - lll
    29 28   - misspell
    30 29   - nakedret
    31 30   - nolintlint
    skipped 24 lines
  • ■ ■ ■ ■ ■ ■
    README.md
    skipped 46 lines
    47 47  ----------
    48 48   
    49 49  ```console
    50  -Usage of csprecon:
    51  - -c int
    52  - Concurrency level (default 100)
    53  - -d string
    54  - Filter results belonging to this domain
    55  - -l string
    56  - File containing input domains
    57  - -o string
    58  - File to write output results
    59  - -s Print only results
    60  - -t int
    61  - Connection timeout in seconds (default 10)
    62  - -u string
    63  - Input domain
    64  - -v Verbose output
     50 +Usage:
     51 + csprecon [flags]
     52 + 
     53 +Flags:
     54 +INPUT:
     55 + -u string Input domain
     56 + -l string File containing input domains
     57 + 
     58 +CONFIGURATIONS:
     59 + -d string[] Filter results belonging to these domains (comma separated)
     60 + -c int Concurrency level (default 100)
     61 + -t int Connection timeout in seconds (default 10)
     62 + 
     63 +OUTPUT:
     64 + -o string File to write output results
     65 + -v Verbose output
     66 + -s Print only results
     67 + 
     68 + 
    65 69  ```
    66 70   
    67 71  Examples :bulb:
    skipped 13 lines
    81 85  echo targets.txt | csprecon
    82 86  ```
    83 87   
    84  -Grab all possible results belonging to a specific target from a list of domains (protocols needed!)
     88 +Grab all possible results belonging to a specific target(s) from a list of domains (protocols needed!)
    85 89  ```bash
    86 90  echo targets.txt | csprecon -d google.com
    87 91  ```
    skipped 23 lines
  • ■ ■ ■ ■ ■ ■
    go.mod
    skipped 4 lines
    5 5  require github.com/projectdiscovery/utils v0.0.3
    6 6   
    7 7  require (
     8 + github.com/PuerkitoBio/goquery v1.8.0 // indirect
     9 + github.com/andybalholm/cascadia v1.3.1 // indirect
     10 + github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
     11 + github.com/projectdiscovery/fileutil v0.0.0-20220705195237-01becc2a8963 // indirect
     12 + github.com/projectdiscovery/stringsutil v0.0.2 // indirect
     13 +)
     14 + 
     15 +require (
    8 16   github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
    9 17   github.com/aymerick/douceur v0.2.0 // indirect
    10 18   github.com/edoardottt/golazy v0.1.2-dev // indirect
    skipped 4 lines
    15 23   github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
    16 24   github.com/modern-go/reflect2 v1.0.1 // indirect
    17 25   github.com/pkg/errors v0.9.1 // indirect
     26 + github.com/projectdiscovery/goflags v0.1.3
    18 27   github.com/projectdiscovery/gologger v1.1.4 // indirect
    19 28   github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
    20 29   golang.org/x/net v0.1.0 // indirect
    skipped 3 lines
  • ■ ■ ■ ■ ■ ■
    go.sum
     1 +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
     2 +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
     3 +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
     4 +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
    1 5  github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
    2 6  github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
    3 7  github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
    4 8  github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
     9 +github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
     10 +github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
    5 11  github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
    6 12  github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
    7 13  github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
    skipped 21 lines
    29 35  github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
    30 36  github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
    31 37  github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
     38 +github.com/projectdiscovery/fileutil v0.0.0-20220705195237-01becc2a8963 h1:4o97N9ftX1J3iKlIRVMPVOVZs4qbCczJvoFF2WA40t4=
     39 +github.com/projectdiscovery/fileutil v0.0.0-20220705195237-01becc2a8963/go.mod h1:DaY7wmLPMleyHDCD/14YApPCDtrARY4J8Eny2ZGsG/g=
     40 +github.com/projectdiscovery/goflags v0.1.3 h1:dnJlg19VkDp1iYkpAod4Tv+OAngr7Mq61LMMpBQlO0M=
     41 +github.com/projectdiscovery/goflags v0.1.3/go.mod h1:/7ZAoY1SVfUcGobTP5QDvGQmrpPDDlBUDIMr7c+r94Q=
    32 42  github.com/projectdiscovery/gologger v1.1.4 h1:qWxGUq7ukHWT849uGPkagPKF3yBPYAsTtMKunQ8O2VI=
    33 43  github.com/projectdiscovery/gologger v1.1.4/go.mod h1:Bhb6Bdx2PV1nMaFLoXNBmHIU85iROS9y1tBuv7T5pMY=
     44 +github.com/projectdiscovery/stringsutil v0.0.0-20220422150559-b54fb5dc6833/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
     45 +github.com/projectdiscovery/stringsutil v0.0.2 h1:uzmw3IVLJSMW1kEg8eCStG/cGbYYZAja8BH3LqqJXMA=
     46 +github.com/projectdiscovery/stringsutil v0.0.2/go.mod h1:EJ3w6bC5fBYjVou6ryzodQq37D5c6qbAYQpGmAy+DC0=
    34 47  github.com/projectdiscovery/utils v0.0.3 h1:pAjZTGYpnATRc6uaNACdiHv4joZ0Ml7Wpu0dudpcGfM=
    35 48  github.com/projectdiscovery/utils v0.0.3/go.mod h1:ne3eSlZlUKuhjHr8FfsfGcGteCzxcbJvFBx4VDBCxK0=
    36 49  github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
    37 50  github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
    38 51  github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
     52 +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
    39 53  github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
    40 54  github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
     55 +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
     56 +github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
     57 +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
     58 +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
     59 +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
    41 60  golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
    42 61  golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
     62 +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
     63 +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
     64 +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
     65 +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
     66 +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
     67 +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
     68 +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
     69 +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
     70 +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
    43 71  gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
    44 72  gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
    45 73  gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
    skipped 4 lines
  • ■ ■ ■ ■ ■
    pkg/csprecon/csp.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "crypto/tls"
    5  - "io"
    6 5   "net"
    7 6   "net/http"
    8 7   "regexp"
    skipped 1 lines
    10 9   "time"
    11 10   
    12 11   "github.com/edoardottt/golazy"
    13  - "golang.org/x/net/html"
    14 12  )
    15 13   
    16 14  const (
    skipped 2 lines
    19 17   DomainRegex = `.*[a-zA-Z\_\-0-9]+\.[a-z]+`
    20 18  )
    21 19   
    22  -func checkCSP(url string, r *regexp.Regexp, client *http.Client) ([]string, error) {
     20 +func checkCSP(url string, rCSP *regexp.Regexp, client *http.Client) ([]string, error) {
    23 21   var (
    24 22   result = []string{}
    25 23   headerCSP []string
    26  - bodyCSP []string
    27 24   )
    28 25   
    29 26   req, err := http.NewRequest(http.MethodGet, url, nil)
    skipped 8 lines
    38 35   
    39 36   defer resp.Body.Close()
    40 37   
    41  - headerCSP = parseCSP(resp.Header.Get("Content-Security-Policy"), r)
    42  - if len(headerCSP) == 0 {
    43  - bodyCSP, err = parseCSPBody(resp.Body, r)
    44  - if err != nil {
    45  - return result, nil
    46  - }
    47  - }
    48  - 
     38 + headerCSP = parseCSP(resp.Header.Get("Content-Security-Policy"), rCSP)
    49 39   result = append(result, headerCSP...)
    50  - result = append(result, bodyCSP...)
    51 40   
    52 41   return result, nil
    53 42  }
    skipped 23 lines
    77 66   return result
    78 67  }
    79 68   
    80  -func parseCSPBody(input io.ReadCloser, r *regexp.Regexp) ([]string, error) {
    81  - result := []string{}
    82  - 
    83  - doc, err := html.Parse(input)
    84  - if err != nil {
    85  - return result, err
    86  - }
    87  - 
    88  - bodyString, err := io.ReadAll(input)
    89  - if err != nil {
    90  - return result, err
    91  - }
    92  - 
    93  - if strings.Contains(string(bodyString), `http-equiv="Content-Security-Policy"`) {
    94  - // Recursively visit nodes in the parse tree
    95  - var f func(*html.Node)
    96  - f = func(n *html.Node) {
    97  - if n.Data == "meta" {
    98  - for _, a := range n.Attr {
    99  - if a.Key == "content" {
    100  - result = parseCSP(a.Val, r)
    101  - break
    102  - }
    103  - }
    104  - }
    105  - 
    106  - for c := n.FirstChild; c != nil; c = c.NextSibling {
    107  - f(c)
    108  - }
    109  - }
    110  - f(doc)
    111  - }
    112  - 
    113  - return result, nil
    114  -}
    115  - 
    116 69  func customClient(timeout int) *http.Client {
    117 70   transport := http.Transport{
    118 71   TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    skipped 18 lines
    137 90   return r
    138 91  }
    139 92   
     93 +func domainOk(input string, domains []string) bool {
     94 + for _, domain := range domains {
     95 + if len(input) > len(domain)+1 && input[len(input)-len(domain)-1:] == "."+domain {
     96 + return true
     97 + }
     98 + }
     99 + 
     100 + return false
     101 +}
     102 + 
  • ■ ■ ■ ■ ■ ■
    pkg/csprecon/csprecon.go
    skipped 3 lines
    4 4   "bufio"
    5 5   "fmt"
    6 6   "os"
    7  - "regexp"
    8 7   "strings"
    9 8   "sync"
    10 9   
    skipped 7 lines
    18 17  type Runner struct {
    19 18   Input chan string
    20 19   Output chan string
     20 + Result output.Result
    21 21   InWg *sync.WaitGroup
    22 22   OutWg *sync.WaitGroup
    23 23   Options input.Options
    skipped 3 lines
    27 27   return Runner{
    28 28   Input: make(chan string, options.Concurrency),
    29 29   Output: make(chan string, options.Concurrency),
     30 + Result: output.New(),
    30 31   InWg: &sync.WaitGroup{},
    31 32   OutWg: &sync.WaitGroup{},
    32 33   Options: *options,
    skipped 42 lines
    75 76  func execute(r *Runner) {
    76 77   defer r.InWg.Done()
    77 78   
    78  - regex := regexp.Regexp{}
    79  - 
    80  - if r.Options.Domain != "" {
    81  - regex = *CompileRegex(`.*\.` + r.Options.Domain)
    82  - }
    83  - 
    84 79   dregex := CompileRegex(DomainRegex)
    85 80   
    86 81   for i := 0; i < r.Options.Concurrency; i++ {
    skipped 16 lines
    103 98   
    104 99   for _, res := range result {
    105 100   if resTrimmed := strings.TrimSpace(res); resTrimmed != "" {
    106  - if r.Options.Domain != "" {
    107  - if regex.Match([]byte(resTrimmed)) {
     101 + if len(r.Options.Domain) != 0 {
     102 + if domainOk(resTrimmed, r.Options.Domain) {
    108 103   r.Output <- resTrimmed
    109 104   }
    110 105   } else {
    skipped 9 lines
    120 115  func pullOutput(r *Runner) {
    121 116   defer r.OutWg.Done()
    122 117   
    123  - out := output.New()
    124  - 
    125 118   for o := range r.Output {
    126  - r.OutWg.Add(1)
     119 + if !r.Result.Printed(o) {
     120 + r.OutWg.Add(1)
    127 121   
    128  - go writeOutput(r.OutWg, &r.Options, &out, o)
     122 + go writeOutput(r.OutWg, &r.Options, o)
     123 + }
    129 124   }
    130 125  }
    131 126   
    132  -func writeOutput(wg *sync.WaitGroup, options *input.Options, out *output.Result, o string) {
     127 +func writeOutput(wg *sync.WaitGroup, options *input.Options, o string) {
    133 128   defer wg.Done()
    134 129   
    135  - if options.FileOutput != "" {
     130 + if options.FileOutput != "" && options.Output == nil {
    136 131   file, err := os.OpenFile(options.FileOutput, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
    137 132   if err != nil {
    138 133   gologger.Fatal().Msg(err.Error())
    skipped 2 lines
    141 136   options.Output = file
    142 137   }
    143 138   
    144  - if !out.Printed(o) {
    145  - if options.Output != nil {
    146  - if _, err := options.Output.Write([]byte(o + "\n")); err != nil && options.Verbose {
    147  - gologger.Fatal().Msg(err.Error())
    148  - }
     139 + if options.Output != nil {
     140 + if _, err := options.Output.Write([]byte(o + "\n")); err != nil && options.Verbose {
     141 + gologger.Fatal().Msg(err.Error())
    149 142   }
    150  - 
    151  - fmt.Println(o)
    152 143   }
     144 + 
     145 + fmt.Println(o)
    153 146  }
    154 147   
  • ■ ■ ■ ■ ■ ■
    pkg/input/flags.go
    1 1  package input
    2 2   
    3 3  import (
    4  - "flag"
    5 4   "io"
    6 5   "os"
    7 6   "strings"
    8 7   
    9 8   "github.com/edoardottt/csprecon/pkg/output"
    10  - 
     9 + "github.com/projectdiscovery/goflags"
    11 10   "github.com/projectdiscovery/gologger"
    12 11   "github.com/projectdiscovery/gologger/levels"
    13 12  )
    skipped 7 lines
    21 20   Input string
    22 21   FileInput string
    23 22   FileOutput string
    24  - Domain string
     23 + Domain goflags.StringSlice
    25 24   Verbose bool
    26 25   Output io.Writer
    27 26   Silent bool
    skipped 14 lines
    42 41  func ParseOptions() *Options {
    43 42   options := &Options{}
    44 43   
    45  - flag.StringVar(&options.Input, "u", "", `Input domain`)
    46  - flag.StringVar(&options.FileInput, "l", "", `File containing input domains`)
    47  - flag.StringVar(&options.Domain, "d", "", `Filter results belonging to this domain`)
    48  - flag.StringVar(&options.FileOutput, "o", "", `File to write output results`)
    49  - flag.BoolVar(&options.Verbose, "v", false, `Verbose output`)
    50  - flag.BoolVar(&options.Silent, "s", false, `Print only results`)
    51  - flag.IntVar(&options.Concurrency, "c", DefaultConcurrency, "Concurrency level")
    52  - flag.IntVar(&options.Timeout, "t", DefaultTimeout, "Connection timeout in seconds")
     44 + flagSet := goflags.NewFlagSet()
     45 + flagSet.SetDescription(`Discover new target domains using Content Security Policy.`)
     46 + 
     47 + // Input
     48 + flagSet.CreateGroup("input", "Input",
     49 + flagSet.StringVar(&options.Input, "u", "", `Input domain`),
     50 + flagSet.StringVar(&options.FileInput, "l", "", `File containing input domains`),
     51 + )
    53 52   
    54  - if help() {
     53 + flagSet.CreateGroup("configs", "Configurations",
     54 + flagSet.StringSliceVar(&options.Domain, "d", nil, `Filter results belonging to these domains (comma separated)`, goflags.CommaSeparatedStringSliceOptions),
     55 + flagSet.IntVar(&options.Concurrency, "c", DefaultConcurrency, `Concurrency level`),
     56 + flagSet.IntVar(&options.Timeout, "t", DefaultTimeout, `Connection timeout in seconds`),
     57 + )
     58 + 
     59 + // Output
     60 + flagSet.CreateGroup("output", "Output",
     61 + flagSet.StringVar(&options.FileOutput, "o", "", `File to write output results`),
     62 + flagSet.BoolVar(&options.Verbose, "v", false, `Verbose output`),
     63 + flagSet.BoolVar(&options.Silent, "s", false, `Print only results`),
     64 + )
     65 + 
     66 + if help() || noArgs() || !options.Silent {
    55 67   output.ShowBanner()
    56 68   }
    57 69   
    58  - flag.Parse()
     70 + if err := flagSet.Parse(); err != nil {
     71 + output.ShowBanner()
     72 + gologger.Fatal().Msgf("%s\n", err)
     73 + }
    59 74   
    60 75   // Read the inputs and configure the logging.
    61 76   options.configureOutput()
    62  - 
    63  - if !options.Silent {
    64  - output.ShowBanner()
    65  - }
    66 77   
    67 78   if err := options.validateOptions(); err != nil {
    68 79   gologger.Fatal().Msgf("%s\n", err)
    skipped 14 lines
    83 94   return false
    84 95  }
    85 96   
     97 +func noArgs() bool {
     98 + // User passed no flag.
     99 + return len(os.Args) < 2
     100 +}
     101 + 
  • ■ ■ ■ ■ ■
    pkg/output/output.go
    skipped 4 lines
    5 5  )
    6 6   
    7 7  type Result struct {
    8  - Map sync.Map
     8 + Map map[string]struct{}
     9 + Mutex *sync.RWMutex
    9 10  }
    10 11   
    11 12  func New() Result {
    12  - return Result{Map: sync.Map{}}
     13 + return Result{
     14 + Map: map[string]struct{}{},
     15 + Mutex: &sync.RWMutex{},
     16 + }
    13 17  }
    14 18   
    15 19  func (o *Result) Printed(result string) bool {
    16  - if _, ok := o.Map.Load(result); !ok {
    17  - o.Map.Store(result, true)
     20 + o.Mutex.RLock()
     21 + if _, ok := o.Map[result]; !ok {
     22 + o.Mutex.RUnlock()
     23 + o.Mutex.Lock()
     24 + o.Map[result] = struct{}{}
     25 + o.Mutex.Unlock()
     26 + 
    18 27   return false
     28 + } else {
     29 + o.Mutex.RUnlock()
    19 30   }
    20 31   
    21 32   return true
    skipped 2 lines
Please wait...
Page is in error, reload to recover