Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

278 lines
7.0KB

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