Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

296 lines
7.4KB

  1. package main
  2. import (
  3. "crypto/sha1"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "log"
  8. "net/http"
  9. "net/http/httputil"
  10. "net/url"
  11. "os"
  12. "reflect"
  13. "sort"
  14. "strings"
  15. )
  16. //apiV1Main version 1 main entry for all wechat callbacks
  17. //
  18. func apiV1Main(w http.ResponseWriter, r *http.Request) {
  19. logRequestDebug(httputil.DumpRequest(r, true))
  20. if checkSignature(r) == false {
  21. log.Println("signature of URL incorrect")
  22. w.Header().Set("Content-Type", "text/xml; charset=utf-8")
  23. w.WriteHeader(http.StatusUnauthorized)
  24. fmt.Fprintf(w, "") //empty string
  25. return
  26. }
  27. switch r.Method {
  28. case "POST":
  29. //answerWechatPostEcho(w, r)
  30. answerWechatPost(w, r)
  31. case "GET":
  32. answerInitialAuth(w, r)
  33. default:
  34. log.Println(fmt.Sprintf("FATAL: Unhandled HTTP %s", r.Method))
  35. response404Handler(w)
  36. //fmt.Fprintf(w, "Protocol Error: Expect GET or POST only")
  37. }
  38. }
  39. //
  40. //answerInitialAuth, when wechat first verify our URL for API hooks
  41. //
  42. func answerInitialAuth(w http.ResponseWriter, r *http.Request) {
  43. rq := r.URL.RawQuery
  44. m, _ := url.ParseQuery(rq)
  45. echostr, eok := m["echostr"]
  46. if checkSignature(r) && eok {
  47. fmt.Fprintf(w, echostr[0])
  48. } else {
  49. fmt.Fprintf(w, "wtf is wrong with the Internet")
  50. }
  51. }
  52. //
  53. //InWechatMsg what we received currently from wechat
  54. type InWechatMsg struct {
  55. header CommonHeader
  56. body interface{} //dynamic type
  57. }
  58. //
  59. //answerWechatPost distribute PostRequest according to xml body info
  60. func answerWechatPost(w http.ResponseWriter, r *http.Request) {
  61. in, valid := readWechatInput(r)
  62. //reply := "" //nothing
  63. if !valid {
  64. log.Println("Error: Invalid Input ")
  65. }
  66. AllInMessage <- in
  67. openID := in.header.FromUserName
  68. if openID == "" {
  69. log.Println("nothing")
  70. }
  71. //dummy reply
  72. reply, _ := BuildTextMsg(openID, "澳")
  73. w.Header().Set("Content-Type", "text/xml; charset=utf-8")
  74. fmt.Fprint(w, reply)
  75. //time.Sleep(5 * time.Second)
  76. v := reflect.ValueOf(answerWechatPost)
  77. log.Printf("Current Pointer: %d", v.Pointer())
  78. //debug.PrintStack()
  79. //load session into memory
  80. // session := startSession(openID, in, w, r) //who , input what?
  81. // session.ProcessInput()
  82. // //put session back to disk
  83. // session.Save()
  84. }
  85. // func (session) ProcessInput(w, r) {
  86. // //are we in an existing procedure
  87. // inProc, state := isInProc(openID) //if inside a procedure, resume last saved state
  88. // if inProc {
  89. // state = serveProc(state, in) //transit to new state
  90. // reply = state.response //xml response
  91. // } else {
  92. // state, processed := serveCommand(openID, in) //menu or txt command e.g. search
  93. // if !processed { // transfer to Customer Service (kf)
  94. // reply = buildKfForwardMsg(openID, "")
  95. // kfSendTxt(openID, "未识别的命令,已转接校友会理事会,稍后答复您")
  96. // } else {
  97. // reply = state.response
  98. // }
  99. // }
  100. // log.Println(reply) //instant reply, answering user's request
  101. // w.Header().Set("Content-Type", "text/xml; charset=utf-8")
  102. // fmt.Fprint(w, reply)
  103. // if !isEndingState(state) {
  104. // err := saveChatState(openID, state.Procedure, state)
  105. // if err != nil {
  106. // log.Println("Error Cannot Save chat sate")
  107. // log.Println(err)
  108. // log.Println(state)
  109. // }
  110. // } else { //state ending
  111. // cleanProcedure(openID, state.Procedure)
  112. // }
  113. // return
  114. // }
  115. func readWechatInput(r *http.Request) (result InWechatMsg, valid bool) {
  116. body, err := ioutil.ReadAll(r.Body)
  117. if err != nil {
  118. log.Println(err)
  119. valid = false
  120. return
  121. }
  122. d := decryptToXML(string(body))
  123. if d == "" {
  124. log.Println("Cannot decode Message : \r\n" + string(body))
  125. valid = false
  126. return
  127. }
  128. valid = true
  129. fmt.Printf("decrypt as: %s\n", d)
  130. result.header = ReadCommonHeader(d)
  131. switch result.header.MsgType {
  132. case "text":
  133. result.body = ReadTextMsg(d)
  134. case "image":
  135. result.body = ReadPicMsg(d)
  136. case "voice":
  137. result.body = ReadVoiceMsg(d)
  138. case "video":
  139. result.body = ReadVideoMsg(d)
  140. case "shortvideo":
  141. result.body = ReadShortVideoMsg(d)
  142. case "location":
  143. result.body = ReadLocationMsg(d)
  144. case "link":
  145. result.body = ReadLinkMsg(d)
  146. case "event":
  147. result.body = ReadEventMsg(d)
  148. default:
  149. log.Println("Fatal: unknown incoming message type" + result.header.MsgType)
  150. valid = false
  151. }
  152. return
  153. }
  154. func answerWechatPostEcho(w http.ResponseWriter, r *http.Request) {
  155. body, _ := ioutil.ReadAll(r.Body)
  156. //fmt.Printf("get body: %s", string(body))
  157. s := ReadEncryptedMsg(string(body))
  158. //fmt.Printf("to decrypt %s", s.Encrypt)
  159. d := Decode(s.Encrypt)
  160. fmt.Printf("echo as: \r\n%s", d)
  161. e := strings.Replace(d, "ToUserName", "FDDD20170506xyzunique", 2)
  162. f := strings.Replace(e, "FromUserName", "ToUserName", 2)
  163. g := strings.Replace(f, "FDDD20170506xyzunique", "FromUserName", 2)
  164. fmt.Fprint(w, g)
  165. }
  166. //
  167. func checkSignature(r *http.Request) bool {
  168. rq := r.URL.RawQuery
  169. m, _ := url.ParseQuery(rq)
  170. signature, sok := m["signature"]
  171. timestamp, tok := m["timestamp"]
  172. nonce, nok := m["nonce"]
  173. token := APIConfig.Token
  174. if sok && tok && nok {
  175. //sort token, timestamp, nonce and join them
  176. strs := []string{token, timestamp[0], nonce[0]}
  177. sort.Strings(strs)
  178. s := strings.Join(strs, "")
  179. //calculate sha1
  180. h := sha1.New()
  181. h.Write([]byte(s))
  182. calculated := fmt.Sprintf("%x", h.Sum(nil))
  183. return signature[0] == calculated
  184. }
  185. return false
  186. }
  187. func checkSignature1() bool {
  188. s1 := "e39de9f2e28079c01ebb4b803dfc3442b819545c"
  189. t1 := "1492970761"
  190. n1 := "1850971833"
  191. token := APIConfig.Token
  192. strs := []string{token, t1, n1}
  193. sort.Strings(strs)
  194. s := strings.Join(strs, "")
  195. h := sha1.New()
  196. h.Write([]byte(s))
  197. us := fmt.Sprintf("%x", h.Sum(nil))
  198. return s1 == us
  199. }
  200. //webrootHandler sending contents to client when request "/"
  201. // essentially to prove the webserver is still alive
  202. // echo query string to the client
  203. func webrootHandler(w http.ResponseWriter, r *http.Request) {
  204. fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
  205. rq := r.URL.RawQuery
  206. m, _ := url.ParseQuery(rq)
  207. for index, element := range m {
  208. fmt.Fprintf(w, "\n%s => %s", index, element)
  209. }
  210. logRequestDebug(httputil.DumpRequest(r, true))
  211. }
  212. func logRequestDebug(data []byte, err error) {
  213. if err == nil {
  214. fmt.Printf("%s\n\n", string(data))
  215. } else {
  216. log.Fatalf("%s\n\n", err)
  217. }
  218. }
  219. //save uploaded 'file' into temporary location
  220. func uploadHandler(w http.ResponseWriter, r *http.Request) {
  221. r.ParseMultipartForm(32 << 20) //32MB memory
  222. file, handler, err := r.FormFile("file") //form-field 'file'
  223. if err != nil {
  224. fmt.Println(err)
  225. return
  226. }
  227. defer file.Close()
  228. fmt.Fprintf(w, "%v", handler.Header)
  229. f, err := os.OpenFile("/tmp/wechat_hitxy_"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
  230. if err != nil {
  231. fmt.Println(err)
  232. return
  233. }
  234. defer f.Close()
  235. io.Copy(f, file)
  236. }
  237. //when user requst an attachment from our server, we get the file from CRM server and reply it back
  238. //in this way, user has no 'direct-access' to the CRM server.
  239. func crmAttachmentHandler(w http.ResponseWriter, r *http.Request) {
  240. fileID := getCrmFileID(r.URL.Path)
  241. saveAs := GlobalPath.CRMAttachment + "crm_attach_" + fileID //add prefix for easy deleting
  242. if isFileExist(saveAs) {
  243. http.ServeFile(w, r, saveAs)
  244. return
  245. }
  246. if fileID == "" {
  247. log.Println("invalid fileID")
  248. response404Handler(w)
  249. return
  250. }
  251. log.Printf("download %s => %s", fileID, saveAs)
  252. crmDownloadAttachmentAs(fileID, saveAs)
  253. if !isFileExist(saveAs) {
  254. response404Handler(w)
  255. return
  256. }
  257. http.ServeFile(w, r, saveAs)
  258. }
  259. func getCrmFileID(urlPath string) string {
  260. return strings.TrimPrefix(urlPath, "/crmfiles/")
  261. }
  262. func response404Handler(w http.ResponseWriter) {
  263. w.WriteHeader(http.StatusNotFound)
  264. fmt.Fprintf(w, "not found")
  265. }