|
- package main
-
- import (
- "crypto/sha1"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "net/http"
- "net/http/httputil"
- "net/url"
- "os"
- "sort"
- "strconv"
- "strings"
- "time"
- )
-
- //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")
- }
- }
-
- //
- //answerWechatPost distribute PostRequest according to xml body info
- func answerWechatPost(w http.ResponseWriter, r *http.Request) {
- in, valid := readWechatInput(r)
- reply := "" //nothing
- w.Header().Set("Content-Type", "text/xml; charset=utf-8")
-
- if !valid {
- log.Println("Error: Invalid Input ")
- } else {
- //put into user session based pipeline
- AllInMessage <- &in
- //read instant response
- reply = <-in.instantResponse
- }
-
- in.destroy()
- //uncomment the followin two lines to enable echo test
- // state, _ := echoCommand(in.header.FromUserName, in)
- // reply = state.response
- fmt.Fprint(w, reply)
- }
-
- 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.init()
- 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 {
- return checkSignatureByToken(r, APIConfig.Token)
- }
-
- func checkSignatureByToken(r *http.Request, token string) bool {
- rq := r.URL.RawQuery
- m, _ := url.ParseQuery(rq)
-
- signature, sok := m["signature"]
- timestamp, tok := m["timestamp"]
- nonce, nok := m["nonce"]
- token = strings.TrimSpace(token)
- if sok && tok && nok && token != "" {
- return verifySignature(signature[0], timestamp[0], nonce[0], token)
- }
- return false
- }
-
- func checkCookieSignatureBytoken(r *http.Request, token string) bool {
- signature := ""
- nonce := ""
- timestamp := ""
- for _, c := range r.Cookies() {
- switch c.Name {
- case "signature":
- signature = c.Value
- case "nonce":
- nonce = c.Value
- case "timestamp":
- timestamp = c.Value
- }
- }
- if signature != "" && nonce != "" && timestamp != "" && token != "" {
- return verifySignature(signature, timestamp, nonce, IntraAPIConfig.CRMSecrete)
- }
- return false
- }
-
- func verifySignature(signature, timestamp, nonce, token string) bool {
- if timestampTooOldStr(timestamp) {
- return false
- }
- return signature == calculateSignature(timestamp, nonce, token)
- }
-
- func calculateSignature(timestamp, nonce, token string) (signature string) {
- //sort token, timestamp, nonce and join them
- 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))
- return calculated
-
- }
- func timestampTooOldStr(timestamp string) bool {
- ts, err := strconv.Atoi(timestamp)
- if err != nil {
- return true
- }
- return timestampTooOld(int32(ts))
- }
-
- func timestampTooOld(ts int32) bool {
- //diff > 3min from now
- now := int32(time.Now().Unix())
- diff := now - ts
- if diff < 0 {
- diff = -diff
- }
- return diff > 180 //3 minutes, 180 seconds
- }
-
- func timestampOldThan(ts int32, sec int32) bool {
- //diff > 3min from now
- now := int32(time.Now().Unix())
- diff := now - ts
- return diff > sec
- }
-
- // 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 := CRMConfig.AttachmentCache + "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")
- }
-
- func getHTTPRequestQuery(r *http.Request, name string) (value string) {
- rq := r.URL.RawQuery
- m, _ := url.ParseQuery(rq)
- v, ok := m[name]
- if ok {
- value = v[0]
- }
- return
- }
|