package main import ( "crypto/sha1" "fmt" "io" "io/ioutil" "log" "net/http" "net/http/httputil" "net/url" "os" "sort" "strings" ) //apiV1Main version 1 main entry for all wechat callbacks // func apiV1Main(w http.ResponseWriter, r *http.Request) { logRequestDebug(httputil.DumpRequest(r, true)) if checkSignature(r) == false { log.Println("signature of URL incorrect") w.Header().Set("Content-Type", "text/xml; charset=utf-8") w.WriteHeader(http.StatusUnauthorized) fmt.Fprintf(w, "") //empty string return } switch r.Method { case "POST": //answerWechatPostEcho(w, r) answerWechatPost(w, r) case "GET": answerInitialAuth(w, r) default: log.Println(fmt.Sprintf("FATAL: Unhandled HTTP %s", r.Method)) response404Handler(w) //fmt.Fprintf(w, "Protocol Error: Expect GET or POST only") } } // //answerInitialAuth, when wechat first verify our URL for API hooks // func answerInitialAuth(w http.ResponseWriter, r *http.Request) { rq := r.URL.RawQuery m, _ := url.ParseQuery(rq) echostr, eok := m["echostr"] if checkSignature(r) && eok { fmt.Fprintf(w, echostr[0]) } else { fmt.Fprintf(w, "wtf is wrong with the Internet") } } // //InWechatMsg what we received currently from wechat type InWechatMsg struct { header CommonHeader body interface{} //dynamic type } // //answerWechatPost distribute PostRequest according to xml body info func answerWechatPost(w http.ResponseWriter, r *http.Request) { in, valid := readWechatInput(r) reply := "" //nothing if !valid { log.Println("Error: Invalid Input ") } //are we in an existing procedure openID := in.header.FromUserName yes, state := isInProc(openID) if yes { state := serveProc(openID, in) reply = state.response } else { state, processed := serveCommand(openID, in) //search or other command if !processed { // transfer to Customer Service (kf) reply = buildKfForwardMsg(openID, "") } else { reply = state.response } } log.Println(reply) w.Header().Set("Content-Type", "text/xml; charset=utf-8") fmt.Fprint(w, reply) saveChatState(state) return } func readWechatInput(r *http.Request) (result InWechatMsg, valid bool) { body, err := ioutil.ReadAll(r.Body) if err != nil { log.Println(err) valid = false return } d := decryptToXML(string(body)) if d == "" { log.Println("Cannot decode Message : \r\n" + string(body)) valid = false return } valid = true fmt.Printf("decrypt as: %s\n", d) result.header = ReadCommonHeader(d) switch result.header.MsgType { case "text": result.body = ReadTextMsg(d) case "image": result.body = ReadPicMsg(d) case "voice": result.body = ReadVoiceMsg(d) case "video": result.body = ReadVideoMsg(d) case "shortvideo": result.body = ReadShortVideoMsg(d) case "location": result.body = ReadLocationMsg(d) case "link": result.body = ReadLinkMsg(d) case "event": result.body = ReadEventMsg(d) default: log.Println("Fatal: unknown incoming message type" + result.header.MsgType) valid = false } return } func answerWechatPostEcho(w http.ResponseWriter, r *http.Request) { body, _ := ioutil.ReadAll(r.Body) //fmt.Printf("get body: %s", string(body)) s := ReadEncryptedMsg(string(body)) //fmt.Printf("to decrypt %s", s.Encrypt) d := Decode(s.Encrypt) fmt.Printf("echo as: \r\n%s", d) e := strings.Replace(d, "ToUserName", "FDDD20170506xyzunique", 2) f := strings.Replace(e, "FromUserName", "ToUserName", 2) g := strings.Replace(f, "FDDD20170506xyzunique", "FromUserName", 2) fmt.Fprint(w, g) } // func checkSignature(r *http.Request) bool { rq := r.URL.RawQuery m, _ := url.ParseQuery(rq) signature, sok := m["signature"] timestamp, tok := m["timestamp"] nonce, nok := m["nonce"] token := APIConfig.Token if sok && tok && nok { //sort token, timestamp, nonce and join them strs := []string{token, timestamp[0], nonce[0]} sort.Strings(strs) s := strings.Join(strs, "") //calculate sha1 h := sha1.New() h.Write([]byte(s)) calculated := fmt.Sprintf("%x", h.Sum(nil)) return signature[0] == calculated } return false } func checkSignature1() bool { s1 := "e39de9f2e28079c01ebb4b803dfc3442b819545c" t1 := "1492970761" n1 := "1850971833" token := APIConfig.Token strs := []string{token, t1, n1} sort.Strings(strs) s := strings.Join(strs, "") h := sha1.New() h.Write([]byte(s)) us := fmt.Sprintf("%x", h.Sum(nil)) return s1 == us } //webrootHandler sending contents to client when request "/" // essentially to prove the webserver is still alive // echo query string to the client func webrootHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:]) rq := r.URL.RawQuery m, _ := url.ParseQuery(rq) for index, element := range m { fmt.Fprintf(w, "\n%s => %s", index, element) } logRequestDebug(httputil.DumpRequest(r, true)) } func logRequestDebug(data []byte, err error) { if err == nil { fmt.Printf("%s\n\n", string(data)) } else { log.Fatalf("%s\n\n", err) } } //save uploaded 'file' into temporary location func uploadHandler(w http.ResponseWriter, r *http.Request) { r.ParseMultipartForm(32 << 20) //32MB memory file, handler, err := r.FormFile("file") //form-field 'file' if err != nil { fmt.Println(err) return } defer file.Close() fmt.Fprintf(w, "%v", handler.Header) f, err := os.OpenFile("/tmp/wechat_hitxy_"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) if err != nil { fmt.Println(err) return } defer f.Close() io.Copy(f, file) } //when user requst an attachment from our server, we get the file from CRM server and reply it back //in this way, user has no 'direct-access' to the CRM server. func crmAttachmentHandler(w http.ResponseWriter, r *http.Request) { fileID := getCrmFileID(r.URL.Path) saveAs := GlobalPath.CRMAttachment + "crm_attach_" + fileID //add prefix for easy deleting if isFileExist(saveAs) { http.ServeFile(w, r, saveAs) return } if fileID == "" { log.Println("invalid fileID") response404Handler(w) return } log.Printf("download %s => %s", fileID, saveAs) crmDownloadAttachmentAs(fileID, saveAs) if !isFileExist(saveAs) { response404Handler(w) return } http.ServeFile(w, r, saveAs) } func getCrmFileID(urlPath string) string { return strings.TrimPrefix(urlPath, "/crmfiles/") } func response404Handler(w http.ResponseWriter) { w.WriteHeader(http.StatusNotFound) fmt.Fprintf(w, "not found") }