package pollute import ( "bytes" "compress/gzip" "encoding/json" "errors" "fmt" "github.com/PuerkitoBio/goquery" "github.com/xxjwxc/gowp/workpool" "io/ioutil" "math/rand" "net/http" "net/url" "stat/common/log" "strings" "time" ) //http://www.ce8.com/check type ce8PolluteServerConfig struct { DotNetApi string `json:"dotNetApi"` } type ce8PollutionFullReq struct { URL string `json:"url"` } type ce8PollutionFullResp struct { Code int `json:"code"` State bool `json:"state"` Runtime interface{} `json:"runtime"` Data struct { URL string `json:"url"` Pollution bool `json:"pollution"` PollutionNodeCount int `json:"pollution_node_count"` NodeList []struct { PollutionResult struct { IP string `json:"ip"` Location string `json:"location"` Pollution bool `json:"pollution"` IsTimeout bool `json:"is_timeout"` } `json:"pollution_result"` } `json:"node_list"` } `json:"data"` } func ce8PolluteCheck(domains []CheckReq) ([]CheckResult, error) { if len(domains) == 0 { return nil, nil } doc, err := ce8GetCheckDoc() if err != nil { log.Warn("ce8PolluteCheck ce8GetCheckDoc err", log.Any("domain", domains), log.E(err)) return nil, err } sc, err := ce8GetPolluteServerConfig(doc) if err != nil { log.Warn("ce8PolluteCheck ce8GetPolluteServerConfig err", log.Any("domain", domains), log.E(err)) return nil, err } csrfToken := ce8GetCSRFToken(doc) if csrfToken == "" { log.Warn("ce8PolluteCheck ce8GetCSRFToken empty", log.Any("domain", domains)) } log.Info("ce8PolluteCheck ce8PollutionFulls start", log.Any("domainsLen", len(domains))) checkResult, err := ce8PollutionFulls(domains, *sc, csrfToken) if err != nil { log.Info("ce8PolluteCheck ce8PollutionFulls Finish Err", log.Any("domain", domains), log.E(err)) return nil, err } log.Info("ce8PolluteCheck ce8PollutionFulls Finish OK", log.Any("checkResultLen", len(checkResult))) return checkResult, err } func ce8GetCheckDoc() (*goquery.Document, error) { const ce8CheckURL = "https://ce8.com/define" req, err := http.NewRequest("GET", ce8CheckURL, nil) if err != nil { log.Warn("ce8GetCheckDoc newRequest", log.Any("url", ce8CheckURL), log.E(err)) return nil, err } req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9") req.Header.Add("Accept-Language", " en-US,en;q=0.9") req.Header.Add("Accept-Encoding", "gzip") req.Header.Add("Cache-Control", "no-cache") req.Header.Add("Connection", "keep-alive") req.Header.Add("Host", "www.ce8.com") req.Header.Add("Pragma", "no-cache") req.Header.Add("sec-ch-ua", "\"Chromium\";v=\"88\", \"Google Chrome\";v=\"88\", \";Not A Brand\";v=\"99\"") req.Header.Add("sec-ch-ua-mobile", "?0") req.Header.Add("Sec-Fetch-Dest", "document") req.Header.Add("Sec-Fetch-Mode", "navigate") req.Header.Add("Sec-Fetch-Site", "none") req.Header.Add("Sec-Fetch-User", "?1") req.Header.Add("Upgrade-Insecure-Requests", "1") req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36") res, err := http.DefaultClient.Do(req) if err != nil { log.Warn("ce8GetCheckDoc Client Do", log.Any("url", ce8CheckURL), log.E(err)) return nil, err } defer func() { _ = res.Body.Close() }() if res.StatusCode != http.StatusOK { log.Warn("ce8GetCheckDoc err http statusCode", log.Any("statusCode", res.StatusCode)) return nil, errors.New(fmt.Sprintf("StatusCode:%d", res.StatusCode)) } bodyReader := res.Body switch res.Header.Get("Content-Encoding") { case "gzip": bodyReader, err = gzip.NewReader(res.Body) if err != nil { log.Warn("ce8GetCheckDoc gzip newReader", log.E(err)) return nil, err } defer func() { _ = bodyReader.Close() }() default: } doc, err := goquery.NewDocumentFromReader(bodyReader) if err != nil { log.Warn("ce8GetCheckDoc NewDocumentFromReader err", log.Any("url", ce8CheckURL), log.E(err)) return nil, err } return doc, nil } func ce8GetPolluteServerConfig(doc *goquery.Document) (*ce8PolluteServerConfig, error) { script := doc.Find("script").Text() scriptLines := strings.Split(script, "\n") var serverConfigLine = "" for _, line := range scriptLines { if strings.Contains(line, "serverConfig") && strings.Contains(line, "dotNetApi") { serverConfigLine = line break } } if serverConfigLine == "" { log.Warn("ce8GetPolluteServerConfig Get Line Fail") return nil, errors.New("not found serverConfig") } start := strings.Index(serverConfigLine, "{") end := strings.LastIndex(serverConfigLine, "}") if start == -1 || end == -1 { log.Warn("ce8GetPolluteServerConfig err Line Content", log.Any("line", serverConfigLine)) return nil, errors.New("parseServerConfig error") } jsonString := serverConfigLine[start : end+1] var serverConfig ce8PolluteServerConfig err := json.Unmarshal([]byte(jsonString), &serverConfig) if err != nil { log.Warn("ce8GetPolluteServerConfig unmarshal ServerConfig err", log.E(err)) return nil, err } if serverConfig.DotNetApi == "" { log.Warn("ce8GetPolluteServerConfig dotNetApi empty", log.Any("jsonString", jsonString)) return nil, errors.New("serverConfig dotNetApi empty") } return &serverConfig, nil } func ce8GetCSRFToken(doc *goquery.Document) string { var csrfToken = "" doc.Find("meta").EachWithBreak(func(i int, s *goquery.Selection) bool { if name, _ := s.Attr("name"); name == "csrf-token" { csrfToken, _ = s.Attr("content") return false } return true }) return csrfToken } func ce8PollutionFulls(reqs []CheckReq, sc ce8PolluteServerConfig, csrfToken string) ([]CheckResult, error) { ch := make(chan CheckResult, len(reqs)) wp := workpool.New(15) reqs = reqs[:] //copy //在服务器上经常出现超时,这里先做下随机 rand.Shuffle(len(reqs), func(i, j int) { reqs[i], reqs[j] = reqs[j], reqs[i] }) for i := range reqs { req := reqs[i] wp.Do(func() error { domain := req.Domain r := CheckResult{Domain: domain, Payload: req.Payload, Err: errors.New("exception occurred")} polled, err := ce8PollutionFull(domain, sc, csrfToken) r.Polled = polled r.Err = err ch <- r return nil }) } err := wp.Wait() if err != nil { return nil, err } var result []CheckResult count := len(ch) for i := 0; i < count; i++ { result = append(result, <-ch) } close(ch) ch = nil return result, nil } func ce8PollutionFull(domain string, sc ce8PolluteServerConfig, csrfToken string) (bool, error) { u, err := url.Parse(sc.DotNetApi) if err != nil { log.Warn("ce8PollutionFull parseAPI", log.Any("DotNetApi", sc.DotNetApi), log.E(err)) return false, err } u.Path = "/api/define/pollution_full" reqBody, _ := json.Marshal(&ce8PollutionFullReq{URL: domain}) checkURL := u.String() req, err := http.NewRequest("POST", checkURL, bytes.NewReader(reqBody)) if err != nil { log.Warn("ce8PollutionFull newRequest", log.Any("url", checkURL), log.E(err)) return false, err } req.Header.Add("Connection", "keep-alive") req.Header.Add("Pragma", "no-cache") req.Header.Add("Cache-Control", "no-cache") req.Header.Add("Accept", "application/json, text/javascript, */*; q=0.01") if csrfToken != "" { req.Header.Add("X-CSRF-TOKEN", csrfToken) } req.Header.Add("User-Agent", "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36") req.Header.Add("Content-Type", "application/json; charset=UTF-8") req.Header.Add("Origin", "http://www.ce8.com") req.Header.Add("Referer", "http://www.ce8.com/") req.Header.Add("Accept-Language", "en-US,en;q=0.9") client := http.Client{Timeout: 150 * time.Second} res, err := client.Do(req) if err != nil { log.Warn("ce8PollutionFull Client Do", log.Any("checkURL", checkURL), log.Any("domain", domain), log.E(err)) return false, err } defer func() { _ = res.Body.Close() }() if res.StatusCode != http.StatusOK { log.Warn("ce8PollutionFull Resp not OK", log.Any("checkURL", checkURL), log.Any("domain", domain), log.Any("statusCode", res.StatusCode)) return false, errors.New(fmt.Sprintf("StatusCode:%d", res.StatusCode)) } body, err := ioutil.ReadAll(res.Body) if err != nil { log.Warn("ce8PollutionFull Resp ReadAll", log.Any("checkURL", checkURL), log.Any("domain", domain), log.E(err)) return false, err } var fullResp ce8PollutionFullResp err = json.Unmarshal(body, &fullResp) if err != nil { log.Warn("ce8PollutionFull Unmarshal", log.Any("checkURL", checkURL), log.Any("domain", domain), log.Any("body", body), log.E(err)) return false, err } if fullResp.Code == 0 { log.Info("ce8PollutionFull Code OK", log.Any("checkURL", checkURL), log.Any("domain", domain), log.Any("pollution", fullResp.Data.Pollution)) return fullResp.Data.Pollution, nil } log.Warn("ce8PollutionFull Code", log.Any("checkURL", checkURL), log.Any("domain", domain), log.Any("code", fullResp.Code)) return false, errors.New(fmt.Sprintf("respcode expect 0,got:%d", fullResp.Code)) }