diff --git a/crmpixel.go b/crmpixel.go index 65e1e6b..42670da 100644 --- a/crmpixel.go +++ b/crmpixel.go @@ -2,16 +2,131 @@ package main import ( "encoding/base64" + "encoding/json" + "errors" "fmt" + "log" + "math/rand" "net/http" + "strings" + "time" ) -func crmpixel(w http.ResponseWriter, r *http.Request) { +func crmpixelImage() (pixel []byte, err error) { b64pixel := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QcIBzEJ3JY/6wAAAAh0RVh0Q29tbWVudAD2zJa/AAAADUlEQVQI12NgYGBgAAAABQABXvMqOgAAAABJRU5ErkJggg==" - pixel, err := base64.StdEncoding.DecodeString(b64pixel) + pixel, err = base64.StdEncoding.DecodeString(b64pixel) + return +} + +func crmpixel(w http.ResponseWriter, r *http.Request) { + + cookie, lead := crmpixelCookie(r) + log.Println(lead) + http.SetCookie(w, &cookie) + + //send out pixel 1x1 transparent png file + pixel, err := crmpixelImage() if err == nil { w.Write(pixel) } else { fmt.Fprint(w, "decodig wrong") } } + +func crmpixelCookie(r *http.Request) (ret http.Cookie, info crmdLead) { + cookie, leadok := r.Cookie("biukop_cl") //crm lead + + if leadok != nil { + ret, info = createNewCookie(r) + return + } + + valid, info := isValidCookieBiukopCL(cookie.Value) + if !valid { + ret, info = createNewCookie(r) + return + } + + //refresh expiration + ret = *cookie + ret.Expires = time.Now().Add(10 * 365 * 24 * time.Hour) + + return +} + +func createNewCookie(r *http.Request) (ret http.Cookie, info crmdLead) { + expiration := time.Now().Add(10 * 365 * 24 * time.Hour) + info = crmCreateNewAnonymousLeadByHTTPRequest(r) + cookieValue := buildBiukopCLValue(info.ID) + ret = http.Cookie{Name: "biukop_cl", Value: cookieValue, Expires: expiration} + return +} + +func crmCreateNewAnonymousLeadByHTTPRequest(r *http.Request) (info crmdLead) { + info.FirstName = "Anonymous" + info.LastName = "User" + info.Password = "Password" + info.Status = "Anonymous" + info.Description = "Anonymous from " + r.RemoteAddr + info.ForceDuplicate = true + b, err := json.Marshal(info) + if err != nil { + return + } + entity, err := crmCreateEntity("Lead", b) + if err == nil || isErrIndicateDuplicate(err) { + info, _ = entity.(crmdLead) + } + return +} + +func isValidCookieBiukopCL(cookieValue string) (valid bool, info crmdLead) { + valid = false + + //correctly split string + s := strings.Split(cookieValue, "|") + if len(s) != 4 || s[0] == "" { + return + } + id, nonce, timestamp, signature := s[0], s[1], s[2], s[3] + + //check signature + combined := id + nonce + expected := calculateSignature(timestamp, combined, IntraAPIConfig.CRMSecrete) + if expected != signature { + return + } + + //find lead data + info, err := crmpixelLead(id) + if err != nil { + return + } + valid = true + return +} + +func buildBiukopCLsignature(id, nonce string) (timestamp, signature string) { + combined := id + nonce + timestamp = fmt.Sprintf("%d", time.Now().Unix()) + signature = calculateSignature(timestamp, combined, IntraAPIConfig.CRMSecrete) + return +} + +func buildBiukopCLValue(id string) (ret string) { + rand.Seed(time.Now().Unix()) + nonce := fmt.Sprintf("%d", rand.Intn(655352017)) + timestamp, signature := buildBiukopCLsignature(id, nonce) + strs := []string{id, nonce, timestamp, signature} //order is very important + ret = strings.Join(strs, "|") + return +} + +func crmpixelLead(id string) (info crmdLead, err error) { + entity, err := crmGetEntity("Lead", id) + info, ok := entity.(crmdLead) + if !ok { + err = errors.New("search lead, return a bad entity type") + } + return +} diff --git a/crmpixel_test.go b/crmpixel_test.go new file mode 100644 index 0000000..4a24dad --- /dev/null +++ b/crmpixel_test.go @@ -0,0 +1,71 @@ +package main + +import ( + "io/ioutil" + "log" + "net/http" + "testing" +) + +func TestCrmPixelCookie(t *testing.T) { + req := buildReqCrmPixel() + rr, _ := getHTTPResponse(req, crmpixel) + + //check Set-Cookie + v, ok := rr.HeaderMap["Set-Cookie"] //biukop_cl=59610affc2ccce92d|323295325|1499532031|e0010c3e24a201665b783e2c48ab323b87ef52b7; Expires=Tue, 06 Jul 2027 16:40:31 GMT + log.Println(v) + AssertEqual(t, ok, true, "Set-Cookie shold be available") + AssertEqual(t, checkRecorderCookieBiukopCL(v), true, "cookie value should be valid") + + //compare body content + pixel, err := crmpixelImage() + AssertEqual(t, err, nil, "pixel image should have no error") + m, err := ioutil.ReadAll(rr.Body) + AssertEqual(t, testEq(pixel, m), true, "pixel image should be the same") + +} + +func checkRecorderCookieBiukopCL(v []string) bool { + // Copy the Cookie over to a new Request + request := &http.Request{Header: http.Header{"Cookie": v}} + + // Extract the dropped cookie from the request. + cookie, err := request.Cookie("biukop_cl") + + valid, _ := isValidCookieBiukopCL(cookie.Value) + return err == nil && valid +} + +func buildReqCrmPixel() (req *http.Request) { + req, err := http.NewRequest("GET", "/crmpixel.png", nil) + if err != nil { + log.Fatal(err) + } + //buildReqCommonSignature(req, IntraAPIConfig.CRMSecrete) + buildReqCommonHeader(req) + return req +} + +//compare two byte slice +func testEq(a, b []byte) bool { + + if a == nil && b == nil { + return true + } + + if a == nil || b == nil { + return false + } + + if len(a) != len(b) { + return false + } + + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true +} diff --git a/main.go b/main.go index 739d946..0c8fd20 100644 --- a/main.go +++ b/main.go @@ -63,7 +63,7 @@ func setupHTTPHandler() { http.HandleFunc("/profile_newly_register", initialRegistrationHandler) http.HandleFunc("/iapi/getAccessToken", supplyAccessToken) http.HandleFunc("/iapi/createWechatQr", iapiCreateWechatQrCode) - http.HandleFunc("/crmpixel", crmpixel) //tracking pixel. + http.HandleFunc("/crmpixel.png", crmpixel) //tracking pixel. http.ListenAndServe(":65500", nil) } diff --git a/server.go b/server.go index 1fdaa70..3e6910b 100644 --- a/server.go +++ b/server.go @@ -185,15 +185,18 @@ func calculateSignature(timestamp, nonce, token string) (signature string) { strs := []string{token, timestamp, nonce} sort.Strings(strs) s := strings.Join(strs, "") + signature = strSHA1(s) + return +} +func strSHA1(s string) string { //calculate sha1 h := sha1.New() h.Write([]byte(s)) calculated := fmt.Sprintf("%x", h.Sum(nil)) - signature = calculated - return -} + return calculated +} func timestampTooOldStr(timestamp string) bool { ts, err := strconv.Atoi(timestamp) if err != nil {