Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

394 linhas
9.8KB

  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. "strconv"
  14. "strings"
  15. "time"
  16. )
  17. //apiV1Main version 1 main entry for all wechat callbacks
  18. //
  19. func apiV1Main(w http.ResponseWriter, r *http.Request) {
  20. logRequestDebug(httputil.DumpRequest(r, true))
  21. if checkSignature(r) == false {
  22. log.Println("signature of URL incorrect")
  23. w.Header().Set("Content-Type", "text/xml; charset=utf-8")
  24. w.WriteHeader(http.StatusUnauthorized)
  25. fmt.Fprintf(w, "") //empty string
  26. return
  27. }
  28. switch r.Method {
  29. case "POST":
  30. //answerWechatPostEcho(w, r)
  31. answerWechatPost(w, r)
  32. case "GET":
  33. answerInitialAuth(w, r)
  34. default:
  35. log.Println(fmt.Sprintf("FATAL: Unhandled HTTP %s", r.Method))
  36. response404Handler(w)
  37. //fmt.Fprintf(w, "Protocol Error: Expect GET or POST only")
  38. }
  39. }
  40. //
  41. //answerInitialAuth, when wechat first verify our URL for API hooks
  42. //
  43. func answerInitialAuth(w http.ResponseWriter, r *http.Request) {
  44. rq := r.URL.RawQuery
  45. m, _ := url.ParseQuery(rq)
  46. echostr, eok := m["echostr"]
  47. if checkSignature(r) && eok {
  48. fmt.Fprintf(w, echostr[0])
  49. } else {
  50. fmt.Fprintf(w, "wtf is wrong with the Internet")
  51. }
  52. }
  53. //
  54. //answerWechatPost distribute PostRequest according to xml body info
  55. func answerWechatPost(w http.ResponseWriter, r *http.Request) {
  56. in, valid := readWechatInput(r)
  57. reply := "" //nothing
  58. w.Header().Set("Content-Type", "text/xml; charset=utf-8")
  59. if !valid {
  60. log.Println("Error: Invalid Input ")
  61. } else {
  62. //put into user session based pipeline
  63. AllInMessage <- &in
  64. //read instant response
  65. reply = <-in.instantResponse
  66. }
  67. in.destroy()
  68. //uncomment the followin two lines to enable echo test
  69. // state, _ := echoCommand(in.header.FromUserName, in)
  70. // reply = state.response
  71. fmt.Fprint(w, reply)
  72. }
  73. func readWechatInput(r *http.Request) (result InWechatMsg, valid bool) {
  74. body, err := ioutil.ReadAll(r.Body)
  75. if err != nil {
  76. log.Println(err)
  77. valid = false
  78. return
  79. }
  80. d := decryptToXML(string(body))
  81. if d == "" {
  82. log.Println("Cannot decode Message : \r\n" + string(body))
  83. valid = false
  84. return
  85. }
  86. valid = true
  87. fmt.Printf("decrypt as: %s\n", d)
  88. result.init()
  89. result.header = ReadCommonHeader(d)
  90. switch result.header.MsgType {
  91. case "text":
  92. result.body = ReadTextMsg(d)
  93. case "image":
  94. result.body = ReadPicMsg(d)
  95. case "voice":
  96. result.body = ReadVoiceMsg(d)
  97. case "video":
  98. result.body = ReadVideoMsg(d)
  99. case "shortvideo":
  100. result.body = ReadShortVideoMsg(d)
  101. case "location":
  102. result.body = ReadLocationMsg(d)
  103. case "link":
  104. result.body = ReadLinkMsg(d)
  105. case "event":
  106. result.body = ReadEventMsg(d)
  107. default:
  108. log.Println("Fatal: unknown incoming message type" + result.header.MsgType)
  109. valid = false
  110. }
  111. return
  112. }
  113. func answerWechatPostEcho(w http.ResponseWriter, r *http.Request) {
  114. body, _ := ioutil.ReadAll(r.Body)
  115. //fmt.Printf("get body: %s", string(body))
  116. s := ReadEncryptedMsg(string(body))
  117. //fmt.Printf("to decrypt %s", s.Encrypt)
  118. d := Decode(s.Encrypt)
  119. fmt.Printf("echo as: \r\n%s", d)
  120. e := strings.Replace(d, "ToUserName", "FDDD20170506xyzunique", 2)
  121. f := strings.Replace(e, "FromUserName", "ToUserName", 2)
  122. g := strings.Replace(f, "FDDD20170506xyzunique", "FromUserName", 2)
  123. fmt.Fprint(w, g)
  124. }
  125. //
  126. func checkSignature(r *http.Request) bool {
  127. return checkSignatureByToken(r, APIConfig.Token)
  128. }
  129. func checkSignatureByTokenWithExpireSeconds(r *http.Request, token string, expireSeconds int) bool {
  130. rq := r.URL.RawQuery
  131. m, _ := url.ParseQuery(rq)
  132. signature, sok := m["signature"]
  133. timestamp, tok := m["timestamp"]
  134. nonce, nok := m["nonce"]
  135. token = strings.TrimSpace(token)
  136. if sok && tok && nok && token != "" {
  137. return verifySignature(signature[0], timestamp[0], nonce[0], token, expireSeconds)
  138. }
  139. return false
  140. }
  141. func checkSignatureByToken(r *http.Request, token string) bool {
  142. return checkSignatureByTokenWithExpireSeconds(r, token, 180) //180=3minutes
  143. }
  144. // func checkCookieSignatureBytoken(r *http.Request, token string) bool {
  145. // signature := ""
  146. // nonce := ""
  147. // timestamp := ""
  148. // for _, c := range r.Cookies() {
  149. // switch c.Name {
  150. // case "signature":
  151. // signature = c.Value
  152. // case "nonce":
  153. // nonce = c.Value
  154. // case "timestamp":
  155. // timestamp = c.Value
  156. // }
  157. // }
  158. // if signature != "" && nonce != "" && timestamp != "" && token != "" {
  159. // return verifySignature(signature, timestamp, nonce, IntraAPIConfig.CRMSecrete, expireSeconds)
  160. // }
  161. // return false
  162. // }
  163. func verifySignature(signature, timestamp, nonce, token string, expireSeconds int) bool {
  164. if timestampTooOldStr(timestamp, expireSeconds) {
  165. return false
  166. }
  167. return signature == calculateSignature(timestamp, nonce, token)
  168. }
  169. func calculateSignature(timestamp, nonce, token string) (signature string) {
  170. //sort token, timestamp, nonce and join them
  171. strs := []string{token, timestamp, nonce}
  172. sort.Strings(strs)
  173. s := strings.Join(strs, "")
  174. signature = strSHA1(s)
  175. return
  176. }
  177. func strSHA1(s string) string {
  178. //calculate sha1
  179. h := sha1.New()
  180. h.Write([]byte(s))
  181. calculated := fmt.Sprintf("%x", h.Sum(nil))
  182. return calculated
  183. }
  184. func timestampTooOldStr(timestamp string, expireSeconds int) bool {
  185. ts, err := strconv.Atoi(timestamp)
  186. if err != nil {
  187. return true
  188. }
  189. return timestampTooOld(int32(ts), expireSeconds)
  190. }
  191. func timestampTooOld(ts int32, expireSeconds int) bool {
  192. //diff > 3min from now
  193. now := int32(time.Now().Unix())
  194. diff := now - ts
  195. if diff < 0 {
  196. diff = -diff
  197. }
  198. return diff > int32(expireSeconds) //3 minutes, 180 seconds
  199. }
  200. func timestampOldThan(ts int32, sec int32) bool {
  201. //diff > 3min from now
  202. now := int32(time.Now().Unix())
  203. diff := now - ts
  204. return diff > sec
  205. }
  206. func buildSignature(token string) (signature, timestamp, nonce string) {
  207. timestamp = fmt.Sprintf("%d", int32(time.Now().Unix()))
  208. nonce = RandStringRunes(10) // "1461107899" //a randome string cut from previous wechat request
  209. signature = calculateSignature(timestamp, nonce, token)
  210. return
  211. }
  212. func buildReqCommonSignature(req *http.Request, token string) {
  213. signature, timestamp, nonce := buildSignature(token)
  214. q := req.URL.Query()
  215. q.Add("signature", signature)
  216. q.Add("timestamp", timestamp)
  217. q.Add("nonce", nonce)
  218. req.URL.RawQuery = q.Encode()
  219. }
  220. func buildSignatureAppend2Url(oldURL string, token string) (newURL string) {
  221. u, err := url.Parse(oldURL)
  222. if err != nil {
  223. log.Println(err)
  224. newURL = oldURL
  225. return
  226. }
  227. signature, timestamp, nonce := buildSignature(token)
  228. q := u.Query()
  229. q.Add("signature", signature)
  230. q.Add("timestamp", timestamp)
  231. q.Add("nonce", nonce)
  232. u.RawQuery = q.Encode()
  233. newURL = u.String()
  234. return
  235. }
  236. // func checkSignature1() bool {
  237. // s1 := "e39de9f2e28079c01ebb4b803dfc3442b819545c"
  238. // t1 := "1492970761"
  239. // n1 := "1850971833"
  240. // token := APIConfig.Token
  241. // strs := []string{token, t1, n1}
  242. // sort.Strings(strs)
  243. // s := strings.Join(strs, "")
  244. // h := sha1.New()
  245. // h.Write([]byte(s))
  246. // us := fmt.Sprintf("%x", h.Sum(nil))
  247. // return s1 == us
  248. // }
  249. //webrootHandler sending contents to client when request "/"
  250. // essentially to prove the webserver is still alive
  251. // echo query string to the client
  252. func webrootHandler(w http.ResponseWriter, r *http.Request) {
  253. fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
  254. rq := r.URL.RawQuery
  255. m, _ := url.ParseQuery(rq)
  256. for index, element := range m {
  257. fmt.Fprintf(w, "\n%s => %s", index, element)
  258. }
  259. logRequestDebug(httputil.DumpRequest(r, true))
  260. }
  261. func logRequestDebug(data []byte, err error) {
  262. if err == nil {
  263. fmt.Printf("%s\n\n", string(data))
  264. } else {
  265. log.Fatalf("%s\n\n", err)
  266. }
  267. }
  268. //save uploaded 'file' into temporary location
  269. func uploadHandler(w http.ResponseWriter, r *http.Request) {
  270. r.ParseMultipartForm(32 << 20) //32MB memory
  271. file, handler, err := r.FormFile("file") //form-field 'file'
  272. if err != nil {
  273. fmt.Println(err)
  274. return
  275. }
  276. defer file.Close()
  277. fmt.Fprintf(w, "%v", handler.Header)
  278. f, err := os.OpenFile("/tmp/wechat_hitxy_"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
  279. if err != nil {
  280. fmt.Println(err)
  281. return
  282. }
  283. defer f.Close()
  284. io.Copy(f, file)
  285. }
  286. //when user requst an attachment from our server, we get the file from CRM server and reply it back
  287. //in this way, user has no 'direct-access' to the CRM server.
  288. func crmAttachmentHandler(w http.ResponseWriter, r *http.Request) {
  289. fileID := getCrmFileID(r.URL.Path)
  290. saveAs := CRMConfig.AttachmentCache + "crm_attach_" + fileID //add prefix for easy deleting
  291. if isFileExist(saveAs) {
  292. http.ServeFile(w, r, saveAs)
  293. return
  294. }
  295. if fileID == "" {
  296. log.Println("invalid fileID")
  297. response404Handler(w)
  298. return
  299. }
  300. log.Printf("download %s => %s", fileID, saveAs)
  301. crmDownloadAttachmentAs(fileID, saveAs)
  302. if !isFileExist(saveAs) {
  303. response404Handler(w)
  304. return
  305. }
  306. http.ServeFile(w, r, saveAs)
  307. }
  308. func getCrmFileID(urlPath string) string {
  309. return strings.TrimPrefix(urlPath, "/crmfiles/")
  310. }
  311. func response400Handler(w http.ResponseWriter) {
  312. w.WriteHeader(http.StatusBadRequest)
  313. str, err := ioutil.ReadFile(GlobalPath.SinglePageEdit + "400.html")
  314. if err == nil {
  315. fmt.Fprintf(w, "%s", str)
  316. return
  317. }
  318. fmt.Fprintf(w, "bad request")
  319. }
  320. func response404Handler(w http.ResponseWriter) {
  321. w.WriteHeader(http.StatusNotFound)
  322. str, err := ioutil.ReadFile(GlobalPath.SinglePageEdit + "404.html")
  323. if err == nil {
  324. fmt.Fprintf(w, "%s", str)
  325. return
  326. }
  327. fmt.Fprintf(w, "not found")
  328. }
  329. func response403Handler(w http.ResponseWriter) {
  330. w.WriteHeader(http.StatusUnauthorized)
  331. str, err := ioutil.ReadFile(GlobalPath.SinglePageEdit + "403.html")
  332. if err == nil {
  333. fmt.Fprintf(w, "%s", str)
  334. return
  335. }
  336. fmt.Fprintf(w, "not authorized")
  337. }
  338. func response500Handler(w http.ResponseWriter) {
  339. w.WriteHeader(http.StatusInternalServerError)
  340. str, err := ioutil.ReadFile(GlobalPath.SinglePageEdit + "500.html")
  341. if err == nil {
  342. fmt.Fprintf(w, "%s", str)
  343. return
  344. }
  345. fmt.Fprintf(w, "server internal error")
  346. }
  347. func getHTTPRequestQuery(r *http.Request, name string) (value string) {
  348. rq := r.URL.RawQuery
  349. m, _ := url.ParseQuery(rq)
  350. v, ok := m[name]
  351. if ok {
  352. value = v[0]
  353. }
  354. return
  355. }