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.Fatalln(fmt.Sprintf("Unhandled HTTP %s", r.Method)) 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") } } //answerWechatPost distribute PostRequest according to xml body info // func answerWechatPost(w http.ResponseWriter, r *http.Request) { body, _ := ioutil.ReadAll(r.Body) d := decryptToXML(string(body)) fmt.Printf("decrypt as: %s\n", d) h := ReadCommonHeader(d) reply, _ := BuildTextMsg(h.MsgType, h.FromUserName) if h.MsgType == "text" { reply, _ = BuildTextMsg("http://www.hitxy.org.au/", h.FromUserName) } if h.MsgType == "voice" { a := ReadVoiceMsg(d) reply, _ = BuildTextMsg(a.Recognition, h.FromUserName) } if h.MsgType == "event" { a := ReadEventMsg(d) if a.Event == "LOCATION" { reply = BuildLocationMsg(0, 0, 0, h.FromUserName) fmt.Printf("output %s", reply) } else { reply, _ = BuildTextMsg(a.Event+"/"+a.EventKey, h.FromUserName) } //mediaID := "cU8BYvAEp3H25V-yGO3WBtMVk2bZcEBgf_kje7V-EPkRA_U4x-OAWb_ONg6Y-Qxt" //video //mediaID := "e2iNEiSxCX5TV1WbFd0TQPTdMx8WbvpkOs_iNhSVQHY" // 236 second mp3 //mediaID := "e2iNEiSxCX5TV1WbFd0TQDVyk970GxTcBWMnqc2RzF0" //5 sec mp3 //mediaID := "e2iNEiSxCX5TV1WbFd0TQMqvVrqFDbDOacdjgQ-OAuE" //news //reply = buildVideoMsg(h.FromUserName, mediaID, "标题", a.Event+"/"+a.EventKey) //reply = buildUploadPicMsg(h.FromUserName, "media_for_test/640x480.jpg") //reply = buildUploadVoiceMsg(h.FromUserName, "media_for_test/music.mp3") //reply = buildVoiceMsg(h.FromUserName, mediaID) reply = buildSampleMusicMsg(h.FromUserName) //reply = buildSampleArticleMsg(h.FromUserName) } w.Header().Set("Content-Type", "text/xml; charset=utf-8") fmt.Fprint(w, reply) 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: \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") }