smtp_server_test.go 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463
  1. package server
  2. import (
  3. "bufio"
  4. "github.com/emersion/go-smtp"
  5. "github.com/stretchr/testify/require"
  6. "io"
  7. "net"
  8. "net/http"
  9. "strings"
  10. "testing"
  11. "time"
  12. )
  13. func TestSmtpBackend_Multipart(t *testing.T) {
  14. email := `EHLO example.com
  15. MAIL FROM: phil@example.com
  16. RCPT TO: ntfy-mytopic@ntfy.sh
  17. DATA
  18. MIME-Version: 1.0
  19. Date: Tue, 28 Dec 2021 00:30:10 +0100
  20. Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
  21. Subject: and one more
  22. From: Phil <phil@example.com>
  23. To: ntfy-mytopic@ntfy.sh
  24. Content-Type: multipart/alternative; boundary="000000000000f3320b05d42915c9"
  25. --000000000000f3320b05d42915c9
  26. Content-Type: text/plain; charset="UTF-8"
  27. what's up
  28. --000000000000f3320b05d42915c9
  29. Content-Type: text/html; charset="UTF-8"
  30. <div dir="ltr">what&#39;s up<br clear="all"><div><br></div></div>
  31. --000000000000f3320b05d42915c9--
  32. .
  33. `
  34. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  35. require.Equal(t, "/mytopic", r.URL.Path)
  36. require.Equal(t, "and one more", r.Header.Get("Title"))
  37. require.Equal(t, "what's up", readAll(t, r.Body))
  38. })
  39. defer s.Close()
  40. defer c.Close()
  41. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  42. }
  43. func TestSmtpBackend_MultipartNoBody(t *testing.T) {
  44. email := `EHLO example.com
  45. MAIL FROM: phil@example.com
  46. RCPT TO: ntfy-emailtest@ntfy.sh
  47. DATA
  48. MIME-Version: 1.0
  49. Date: Tue, 28 Dec 2021 01:33:34 +0100
  50. Message-ID: <CAAvm7ABCDsi9vsuu0WTRXzZQBC8dXrDOLT8iCWdqrsmg@mail.gmail.com>
  51. Subject: This email has a subject but no body
  52. From: Phil <phil@example.com>
  53. To: ntfy-emailtest@ntfy.sh
  54. Content-Type: multipart/alternative; boundary="000000000000bcf4a405d429f8d4"
  55. --000000000000bcf4a405d429f8d4
  56. Content-Type: text/plain; charset="UTF-8"
  57. --000000000000bcf4a405d429f8d4
  58. Content-Type: text/html; charset="UTF-8"
  59. <div dir="ltr"><br></div>
  60. --000000000000bcf4a405d429f8d4--
  61. .
  62. `
  63. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  64. require.Equal(t, "/emailtest", r.URL.Path)
  65. require.Equal(t, "", r.Header.Get("Title")) // We flipped message and body
  66. require.Equal(t, "This email has a subject but no body", readAll(t, r.Body))
  67. })
  68. defer s.Close()
  69. defer c.Close()
  70. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  71. }
  72. func TestSmtpBackend_Plaintext(t *testing.T) {
  73. email := `EHLO example.com
  74. MAIL FROM: phil@example.com
  75. RCPT TO: mytopic@ntfy.sh
  76. DATA
  77. Date: Tue, 28 Dec 2021 00:30:10 +0100
  78. Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
  79. Subject: and one more
  80. From: Phil <phil@example.com>
  81. To: mytopic@ntfy.sh
  82. Content-Type: text/plain; charset="UTF-8"
  83. what's up
  84. .
  85. `
  86. s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  87. require.Equal(t, "/mytopic", r.URL.Path)
  88. require.Equal(t, "and one more", r.Header.Get("Title"))
  89. require.Equal(t, "what's up", readAll(t, r.Body))
  90. })
  91. conf.SMTPServerAddrPrefix = ""
  92. defer s.Close()
  93. defer c.Close()
  94. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  95. }
  96. func TestSmtpBackend_Plaintext_No_ContentType(t *testing.T) {
  97. email := `EHLO example.com
  98. MAIL FROM: phil@example.com
  99. RCPT TO: mytopic@ntfy.sh
  100. DATA
  101. Subject: Very short mail
  102. what's up
  103. .
  104. `
  105. s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  106. require.Equal(t, "/mytopic", r.URL.Path)
  107. require.Equal(t, "Very short mail", r.Header.Get("Title"))
  108. require.Equal(t, "what's up", readAll(t, r.Body))
  109. })
  110. conf.SMTPServerAddrPrefix = ""
  111. defer s.Close()
  112. defer c.Close()
  113. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  114. }
  115. func TestSmtpBackend_Plaintext_EncodedSubject(t *testing.T) {
  116. email := `EHLO example.com
  117. MAIL FROM: phil@example.com
  118. RCPT TO: ntfy-mytopic@ntfy.sh
  119. DATA
  120. Date: Tue, 28 Dec 2021 00:30:10 +0100
  121. Subject: =?UTF-8?B?VGhyZWUgc2FudGFzIPCfjoXwn46F8J+OhQ==?=
  122. From: Phil <phil@example.com>
  123. To: ntfy-mytopic@ntfy.sh
  124. Content-Type: text/plain; charset="UTF-8"
  125. what's up
  126. .
  127. `
  128. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  129. require.Equal(t, "Three santas 🎅🎅🎅", r.Header.Get("Title"))
  130. })
  131. defer s.Close()
  132. defer c.Close()
  133. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  134. }
  135. func TestSmtpBackend_Plaintext_TooLongTruncate(t *testing.T) {
  136. email := `EHLO example.com
  137. MAIL FROM: phil@example.com
  138. RCPT TO: mytopic@ntfy.sh
  139. DATA
  140. Date: Tue, 28 Dec 2021 00:30:10 +0100
  141. Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
  142. Subject: and one more
  143. From: Phil <phil@example.com>
  144. To: mytopic@ntfy.sh
  145. Content-Type: text/plain; charset="UTF-8"
  146. you know this is a string.
  147. it's a long string.
  148. it's supposed to be longer than the max message length
  149. which is 4096 bytes,
  150. it used to be 512 bytes, but I increased that for the UnifiedPush support
  151. the 512 bytes was a little short, some people said
  152. but it kinda makes sense when you look at what it looks like one a phone
  153. heck this wasn't even half of it so far.
  154. so i'm gonna fill the rest of this with AAAAAAAAAAAAAAAAAAAAAAAAAAA
  155. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  156. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
  157. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  158. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  159. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  160. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  161. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  162. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  163. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  164. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  165. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  166. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  167. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  168. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  169. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  170. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  171. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  172. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  173. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  174. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  175. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  176. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  177. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  178. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  179. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  180. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  181. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  182. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  183. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  184. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  185. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  186. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  187. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  188. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  189. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  190. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  191. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  192. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  193. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  194. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  195. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  196. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  197. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  198. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  199. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  200. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  201. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  202. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  203. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  204. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  205. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  206. and with BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
  207. BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
  208. BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
  209. that should do it
  210. .
  211. `
  212. s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  213. expected := `you know this is a string.
  214. it's a long string.
  215. it's supposed to be longer than the max message length
  216. which is 4096 bytes,
  217. it used to be 512 bytes, but I increased that for the UnifiedPush support
  218. the 512 bytes was a little short, some people said
  219. but it kinda makes sense when you look at what it looks like one a phone
  220. heck this wasn't even half of it so far.
  221. so i'm gonna fill the rest of this with AAAAAAAAAAAAAAAAAAAAAAAAAAA
  222. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  223. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
  224. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  225. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  226. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  227. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  228. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  229. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  230. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  231. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  232. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  233. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  234. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  235. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  236. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  237. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  238. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  239. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  240. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  241. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  242. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  243. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  244. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  245. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  246. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  247. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  248. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  249. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  250. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  251. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  252. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  253. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  254. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  255. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  256. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  257. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  258. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  259. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  260. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  261. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  262. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  263. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  264. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  265. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  266. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  267. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  268. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  269. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  270. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  271. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  272. pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
  273. and with BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
  274. BBBBBBBBBBBBBBBBBBBBBBBBB`
  275. require.Equal(t, 4096, len(expected)) // Sanity check
  276. require.Equal(t, expected, readAll(t, r.Body))
  277. })
  278. defer s.Close()
  279. defer c.Close()
  280. conf.SMTPServerAddrPrefix = ""
  281. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  282. }
  283. func TestSmtpBackend_Plaintext_QuotedPrintable(t *testing.T) {
  284. email := `EHLO example.com
  285. MAIL FROM: phil@example.com
  286. RCPT TO: mytopic@ntfy.sh
  287. DATA
  288. Date: Tue, 28 Dec 2021 00:30:10 +0100
  289. Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
  290. Subject: and one more
  291. From: Phil <phil@example.com>
  292. To: mytopic@ntfy.sh
  293. Content-Type: text/plain; charset="UTF-8"
  294. Content-Transfer-Encoding: quoted-printable
  295. what's
  296. =C3=A0&=C3=A9"'(-=C3=A8_=C3=A7=C3=A0)
  297. =3D=3D=3D=3D=3D
  298. up
  299. .
  300. `
  301. s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  302. require.Equal(t, "/mytopic", r.URL.Path)
  303. require.Equal(t, "and one more", r.Header.Get("Title"))
  304. require.Equal(t, `what's
  305. à&é"'(-è_çà)
  306. =====
  307. up`, readAll(t, r.Body))
  308. })
  309. conf.SMTPServerAddrPrefix = ""
  310. defer s.Close()
  311. defer c.Close()
  312. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  313. }
  314. func TestSmtpBackend_Unsupported(t *testing.T) {
  315. email := `EHLO example.com
  316. MAIL FROM: phil@example.com
  317. RCPT TO: ntfy-mytopic@ntfy.sh
  318. DATA
  319. Date: Tue, 28 Dec 2021 00:30:10 +0100
  320. Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
  321. Subject: and one more
  322. From: Phil <phil@example.com>
  323. To: mytopic@ntfy.sh
  324. Content-Type: text/SOMETHINGELSE
  325. what's up
  326. .
  327. `
  328. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  329. t.Fatal("This should not be called")
  330. })
  331. defer s.Close()
  332. defer c.Close()
  333. writeAndReadUntilLine(t, email, c, scanner, "554 5.0.0 Error: transaction failed, blame it on the weather: unsupported content type")
  334. }
  335. func TestSmtpBackend_InvalidAddress(t *testing.T) {
  336. email := `EHLO example.com
  337. MAIL FROM: phil@example.com
  338. RCPT TO: unsupported@ntfy.sh
  339. DATA
  340. Date: Tue, 28 Dec 2021 00:30:10 +0100
  341. Subject: and one more
  342. From: Phil <phil@example.com>
  343. To: mytopic@ntfy.sh
  344. Content-Type: text/plain
  345. what's up
  346. .
  347. `
  348. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  349. t.Fatal("This should not be called")
  350. })
  351. defer s.Close()
  352. defer c.Close()
  353. writeAndReadUntilLine(t, email, c, scanner, "451 4.0.0 invalid address")
  354. }
  355. func TestSmtpBackend_Base64Body(t *testing.T) {
  356. email := `EHLO example.com
  357. MAIL FROM: test@mydomain.me
  358. RCPT TO: ntfy-mytopic@ntfy.sh
  359. DATA
  360. Content-Type: multipart/mixed; boundary="===============2138658284696597373=="
  361. MIME-Version: 1.0
  362. Subject: TrueNAS truenas.local: TrueNAS Test Message hostname: truenas.local
  363. From: =?utf-8?q?Robbie?= <test@mydomain.me>
  364. To: test@mydomain.me
  365. Date: Thu, 16 Feb 2023 01:04:00 -0000
  366. Message-ID: <truenas-20230216.010400.344514.b'8jfL'@truenas.local>
  367. This is a multi-part message in MIME format.
  368. --===============2138658284696597373==
  369. Content-Type: text/plain; charset="utf-8"
  370. MIME-Version: 1.0
  371. Content-Transfer-Encoding: base64
  372. VGhpcyBpcyBhIHRlc3QgbWVzc2FnZSBmcm9tIFRydWVOQVMgQ09SRS4=
  373. --===============2138658284696597373==
  374. Content-Type: text/html; charset="utf-8"
  375. MIME-Version: 1.0
  376. Content-Transfer-Encoding: base64
  377. PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMCBUcmFuc2l0aW9uYWwv
  378. L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
  379. --===============2138658284696597373==--
  380. .
  381. `
  382. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  383. require.Equal(t, "/mytopic", r.URL.Path)
  384. require.Equal(t, "TrueNAS truenas.local: TrueNAS Test Message hostname: truenas.local", r.Header.Get("Title"))
  385. require.Equal(t, "This is a test message from TrueNAS CORE.", readAll(t, r.Body))
  386. })
  387. defer s.Close()
  388. defer c.Close()
  389. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  390. }
  391. func TestSmtpBackend_MultipartQuotedPrintable(t *testing.T) {
  392. email := `EHLO example.com
  393. MAIL FROM: phil@example.com
  394. RCPT TO: ntfy-mytopic@ntfy.sh
  395. DATA
  396. MIME-Version: 1.0
  397. Date: Tue, 28 Dec 2021 00:30:10 +0100
  398. Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
  399. Subject: and one more
  400. From: Phil <phil@example.com>
  401. To: ntfy-mytopic@ntfy.sh
  402. Content-Type: multipart/alternative; boundary="000000000000f3320b05d42915c9"
  403. --000000000000f3320b05d42915c9
  404. Content-Type: text/html; charset="UTF-8"
  405. html, ignore me
  406. --000000000000f3320b05d42915c9
  407. Content-Type: text/plain; charset="UTF-8"
  408. Content-Transfer-Encoding: quoted-printable
  409. what's
  410. =C3=A0&=C3=A9"'(-=C3=A8_=C3=A7=C3=A0)
  411. =3D=3D=3D=3D=3D
  412. up
  413. --000000000000f3320b05d42915c9--
  414. .
  415. `
  416. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  417. require.Equal(t, "/mytopic", r.URL.Path)
  418. require.Equal(t, "and one more", r.Header.Get("Title"))
  419. require.Equal(t, `what's
  420. à&é"'(-è_çà)
  421. =====
  422. up`, readAll(t, r.Body))
  423. })
  424. defer s.Close()
  425. defer c.Close()
  426. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  427. }
  428. func TestSmtpBackend_NestedMultipartBase64(t *testing.T) {
  429. email := `EHLO example.com
  430. MAIL FROM: test@mydomain.me
  431. RCPT TO: ntfy-mytopic@ntfy.sh
  432. DATA
  433. Content-Type: multipart/mixed; boundary="===============2138658284696597373=="
  434. MIME-Version: 1.0
  435. Subject: TrueNAS truenas.local: TrueNAS Test Message hostname: truenas.local
  436. From: =?utf-8?q?Robbie?= <test@mydomain.me>
  437. To: test@mydomain.me
  438. Date: Thu, 16 Feb 2023 01:04:00 -0000
  439. Message-ID: <truenas-20230216.010400.344514.b'8jfL'@truenas.local>
  440. This is a multi-part message in MIME format.
  441. --===============2138658284696597373==
  442. Content-Type: multipart/alternative; boundary="===============2233989480071754745=="
  443. MIME-Version: 1.0
  444. --===============2233989480071754745==
  445. Content-Type: text/plain; charset="utf-8"
  446. MIME-Version: 1.0
  447. Content-Transfer-Encoding: base64
  448. VGhpcyBpcyBhIHRlc3QgbWVzc2FnZSBmcm9tIFRydWVOQVMgQ09SRS4=
  449. --===============2233989480071754745==
  450. Content-Type: text/html; charset="utf-8"
  451. MIME-Version: 1.0
  452. Content-Transfer-Encoding: base64
  453. PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMCBUcmFuc2l0aW9uYWwv
  454. L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
  455. --===============2233989480071754745==--
  456. --===============2138658284696597373==--
  457. .
  458. `
  459. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  460. require.Equal(t, "/mytopic", r.URL.Path)
  461. require.Equal(t, "TrueNAS truenas.local: TrueNAS Test Message hostname: truenas.local", r.Header.Get("Title"))
  462. require.Equal(t, "This is a test message from TrueNAS CORE.", readAll(t, r.Body))
  463. })
  464. defer s.Close()
  465. defer c.Close()
  466. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  467. }
  468. func TestSmtpBackend_NestedMultipartTooDeep(t *testing.T) {
  469. email := `EHLO example.com
  470. MAIL FROM: test@mydomain.me
  471. RCPT TO: ntfy-mytopic@ntfy.sh
  472. DATA
  473. Content-Type: multipart/mixed; boundary="===============1=="
  474. MIME-Version: 1.0
  475. Subject: TrueNAS truenas.local: TrueNAS Test Message hostname: truenas.local
  476. From: =?utf-8?q?Robbie?= <test@mydomain.me>
  477. To: test@mydomain.me
  478. Date: Thu, 16 Feb 2023 01:04:00 -0000
  479. Message-ID: <truenas-20230216.010400.344514.b'8jfL'@truenas.local>
  480. This is a multi-part message in MIME format.
  481. --===============1==
  482. Content-Type: multipart/alternative; boundary="===============2=="
  483. MIME-Version: 1.0
  484. --===============2==
  485. Content-Type: multipart/alternative; boundary="===============3=="
  486. MIME-Version: 1.0
  487. --===============3==
  488. Content-Type: text/plain; charset="utf-8"
  489. MIME-Version: 1.0
  490. Content-Transfer-Encoding: base64
  491. VGhpcyBpcyBhIHRlc3QgbWVzc2FnZSBmcm9tIFRydWVOQVMgQ09SRS4=
  492. --===============3==
  493. Content-Type: text/html; charset="utf-8"
  494. MIME-Version: 1.0
  495. Content-Transfer-Encoding: base64
  496. PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMCBUcmFuc2l0aW9uYWwv
  497. L0VOIj4KClRoaXMgaXMgYSB0ZXN0IG1lc3NhZ2UgZnJvbSBUcnVlTkFTIENPUkUuCg==
  498. --===============3==--
  499. --===============2==--
  500. --===============1==--
  501. .
  502. `
  503. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  504. t.Fatal("This should not be called")
  505. })
  506. defer s.Close()
  507. defer c.Close()
  508. writeAndReadUntilLine(t, email, c, scanner, "554 5.0.0 Error: transaction failed, blame it on the weather: multipart message nested too deep")
  509. }
  510. func TestSmtpBackend_HTMLEmail(t *testing.T) {
  511. email := `EHLO example.com
  512. MAIL FROM: test@mydomain.me
  513. RCPT TO: ntfy-mytopic@ntfy.sh
  514. DATA
  515. Message-Id: <51610934ss4.mmailer@fritz.box>
  516. From: <email@email.com>
  517. To: <email@email.com>,
  518. <ntfy-subjectatntfy@ntfy.sh>
  519. Date: Thu, 30 Mar 2023 02:56:53 +0000
  520. Subject: A HTML email
  521. Mime-Version: 1.0
  522. Content-Type: text/html;
  523. charset="utf-8"
  524. Content-Transfer-Encoding: quoted-printable
  525. <=21DOCTYPE html>
  526. <html>
  527. <head>
  528. <title>Alerttitle</title>
  529. <meta http-equiv=3D"content-type" content=3D"text/html;charset=3Dutf-8"/>
  530. </head>
  531. <body style=3D"color: =23000000; background-color: =23f0eee6;">
  532. <table width=3D"100%" align=3D"center" style=3D"border:solid 2px =23eeeeee=
  533. ; border-collapse: collapse;">
  534. <tr>
  535. <td>
  536. <table style=3D"border-collapse: collapse;">
  537. <tr>
  538. <td style=3D"background: =23FFFFFF;">
  539. <table style=3D"color: =23FFFFFF; background-color: =23006EC0; border-coll=
  540. apse: collapse;">
  541. <tr>
  542. <td style=3D"width: 1000px; text-align: center; font-size: 18pt; font-fami=
  543. ly: Arial, Helvetica, sans-serif; padding: 10px;">
  544. headertext of table
  545. </td>
  546. </tr>
  547. </table>
  548. </td>
  549. </tr>
  550. <tr>
  551. <td style=3D"padding: 10px 20px; background: =23FFFFFF;">
  552. <table style=3D"border-collapse: collapse;">
  553. <tr>
  554. <td style=3D"width: 940px; font-size: 13pt; font-family: Arial, Helvetica,=
  555. sans-serif; text-align: left;">
  556. " Very important information about a change in your
  557. home automation setup
  558. Now the light is on
  559. </td>
  560. </tr>
  561. </table>
  562. </td>
  563. </tr>
  564. <tr>
  565. <td style=3D"padding: 10px 20px; background: =23FFFFFF;">
  566. <table>
  567. <tr>
  568. <td style=3D"width: 960px; font-size: 10pt; font-family: Arial, Helvetica,=
  569. sans-serif; text-align: left;">
  570. <hr />
  571. If you don't want to receive this message anymore, stop the push
  572. services in your <a href=3D"https:fritzbox" target=3D"_=
  573. blank">FRITZ=21Box</a>=2E<br />
  574. Here you can see the active push services: "System > Push Service"=2E
  575. </td>
  576. </tr>
  577. </table>
  578. </td>
  579. </tr>
  580. <tr>
  581. <td>
  582. <table style=3D"color: =23FFFFFF; background-color: =23006EC0;">
  583. <tr>
  584. <td style=3D"width: 1000px; font-size: 10pt; font-family: Arial, Helvetica=
  585. , sans-serif; text-align: center; padding: 10px;">
  586. This mail has ben sent by your <a style=3D"color: =23FFFFFF;" href=3D"https:=
  587. //fritzbox" target=3D"_blank">FRITZ=21Box</a=
  588. > automatically=2E
  589. </td>
  590. </tr>
  591. </table>
  592. </td>
  593. </tr>
  594. </table>
  595. </td>
  596. </tr>
  597. </table>
  598. </body>
  599. </html>
  600. .
  601. `
  602. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  603. require.Equal(t, "/mytopic", r.URL.Path)
  604. require.Equal(t, "A HTML email", r.Header.Get("Title"))
  605. expected := `headertext of table
  606. &#34; Very important information about a change in your
  607. home automation setup
  608. Now the light is on
  609. If you don&#39;t want to receive this message anymore, stop the push
  610. services in your FRITZ!Box .
  611. Here you can see the active push services: &#34;System &gt; Push Service&#34;.
  612. This mail has ben sent by your FRITZ!Box automatically.`
  613. require.Equal(t, expected, readAll(t, r.Body))
  614. })
  615. defer s.Close()
  616. defer c.Close()
  617. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  618. }
  619. const spamEmail = `
  620. EHLO example.com
  621. MAIL FROM: test@mydomain.me
  622. RCPT TO: ntfy-mytopic@ntfy.sh
  623. DATA
  624. Delivered-To: somebody@gmail.com
  625. Received: by 2002:a05:651c:1248:b0:2bf:c263:285 with SMTP id h8csp1096496ljh;
  626. Mon, 30 Oct 2023 06:23:08 -0700 (PDT)
  627. X-Google-Smtp-Source: AGHT+IFsB3WqbwbeefbeefbeefbeefbeefiXRNDHnIy2xBeaYHZCM3EC8DfPv55qDtgq9djTeBCF
  628. X-Received: by 2002:a05:6808:147:b0:3af:66e5:5d3c with SMTP id h7-20020a056808014700b003af66e55d3cmr11662458oie.26.1698672188132;
  629. Mon, 30 Oct 2023 06:23:08 -0700 (PDT)
  630. ARC-Seal: i=1; a=rsa-sha256; t=1698672188; cv=none;
  631. d=google.com; s=arc-20160816;
  632. b=XM96KvnTbr4h6bqrTPTuuDNXmFCr9Be/HvVhu+UsSQjP9RxPk0wDTPUPZ/HWIJs52y
  633. beeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef
  634. BUmQ==
  635. ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816;
  636. h=list-unsubscribe-post:list-unsubscribe:mime-version:subject:to
  637. :reply-to:from:date:message-id:dkim-signature:dkim-signature;
  638. bh=BERwBIp6fBgrZePFKQjyNMmgPkcnq1Zy1jPO8M0T4Ok=;
  639. fh=+kTCcNpX22TOI/SVSLygnrDqWeUt4zW7QKiv0TOVSGs=;
  640. b=lyIBRuOxPOTY2s36OqP7M7awlBKd4t5PX9mJOEJB0eTnTZqML+cplrXUIg2ZTlAAi9
  641. beeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeef
  642. tgVQ==
  643. ARC-Authentication-Results: i=1; mx.google.com;
  644. dkim=pass header.i=@spamspam.com header.s=2020294246 header.b=G8y6xmtK;
  645. dkim=pass header.i=@auth.ccsend.com header.s=1000073432 header.b=ht8IksVK;
  646. spf=pass (google.com: domain of aigxeklyirlg+dvwkrmsgua==_1133104752381_suqcukvbeeynm/owplvdba==@in.constantcontact.com designates 208.75.123.226 as permitted sender) smtp.mailfrom="AigXeKlyIRLG+DvWkRMsGUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com";
  647. dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=spamspam.com
  648. Return-Path: <AigXeKlyIRLG+DvWkRMsGUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com>
  649. Received: from ccm30.constantcontact.com (ccm30.constantcontact.com. [208.75.123.226])
  650. by mx.google.com with ESMTPS id h2-20020a05620a21c200b0076eeed38118si5450962qka.131.2023.10.30.06.23.07
  651. for <somebody@gmail.com>
  652. (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
  653. Mon, 30 Oct 2023 06:23:08 -0700 (PDT)
  654. Received-SPF: pass (google.com: domain of aigxeklyirlg+dvwkrmsgua==_1133104752381_suqcukvbeeynm/owplvdba==@in.constantcontact.com designates 208.75.123.226 as permitted sender) client-ip=208.75.123.226;
  655. Authentication-Results: mx.google.com;
  656. dkim=pass header.i=@spamspam.com header.s=2020294246 header.b=G8y6xmtK;
  657. dkim=pass header.i=@auth.ccsend.com header.s=1000073432 header.b=ht8IksVK;
  658. spf=pass (google.com: domain of aigxeklyirlg+dvwkrmsgua==_1133104752381_suqcukvbeeynm/owplvdba==@in.constantcontact.com designates 208.75.123.226 as permitted sender) smtp.mailfrom="AigXeKlyIRLG+DvWkRMsGUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com";
  659. dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=spamspam.com
  660. Return-Path: <AigXeKlyIRLG+DvWkRMsGUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com>
  661. Received: from [10.252.0.3] ([10.252.0.3:53254] helo=p2-jbemailsyndicator12.ctct.net) by 10.249.225.20 (envelope-from <AigXeKlyIRLG+DvWkRMsGUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com>) (ecelerity 4.3.1.999 r(:)) with ESMTP id A4/82-60517-B3EAF356; Mon, 30 Oct 2023 09:23:07 -0400
  662. DKIM-Signature: v=1; q=dns/txt; a=rsa-sha256; c=relaxed/relaxed; s=2020294246; d=spamspam.com; h=date:mime-version:subject:X-Feedback-ID:X-250ok-CID:message-id:from:reply-to:list-unsubscribe:list-unsubscribe-post:to; bh=BERwBIp6fBgrZePFKQjyNMmgPkcnq1Zy1jPO8M0T4Ok=; b=G8y6xmtKv8asfEXA9o8dP+6foQjclo6j5sFREYVIJBbj5YJ5tqoiv5B04/qoRkoTBFDhmjt+BUua7AqDgPSnwbP2iPSA4fTJehnHhut1PyVUp/9vqSYlhxQehfdhma8tPg8ArKfYIKmfKJwKRaQBU0JHCaB1m+5LNQQX3UjkxAg=
  663. DKIM-Signature: v=1; q=dns/txt; a=rsa-sha256; c=relaxed/relaxed; s=1000073432; d=auth.ccsend.com; h=date:mime-version:subject:X-Feedback-ID:X-250ok-CID:message-id:from:reply-to:list-unsubscribe:list-unsubscribe-post:to; bh=BERwBIp6fBgrZePFKQjyNMmgPkcnq1Zy1jPO8M0T4Ok=; b=ht8IksVKYY/Kb3dUERWoeW4eVdYjKL6F4PEoIZOhfFXor6XAIbPnd3A/CPmbmoqFZjnKh5OdcUy1N5qEoj8w1Q3TmN8/ySQkqrlrmSDSZIHZMY7Qp9/TJrqUe4RMFOO1KKIN6Y0vGP1+dWe98msMAHwvi2qMjG9aEKLfFr2JUTQ=
  664. Message-ID: <1140728754828.1133104752381.1941549819.0.260913JL.2002@synd.ccsend.com>
  665. Date: Mon, 30 Oct 2023 09:23:07 -0400 (EDT)
  666. From: spamspam Loan Servicing <marklake@spamspam.com>
  667. Reply-To: marklake@spamspam.com
  668. To: somebody@gmail.com
  669. Subject: Buying a home? You deserve the confidence of Pre-Approval
  670. MIME-Version: 1.0
  671. Content-Type: multipart/alternative; boundary="----=_Part_75055660_144854819.1698672187348"
  672. List-Unsubscribe: <https://visitor.constantcontact.com/do?p=un&m=beefbeefbeef>
  673. List-Unsubscribe-Post: List-Unsubscribe=One-Click
  674. X-Campaign-Activity-ID: 8a05de2a-5c88-44b1-be0e-f5a444cb0650
  675. X-250ok-CID: 8a05de2a-5c88-44b1-be0e-f5a444cb0650
  676. X-Channel-ID: b1441c50-a541-11ec-a79b-fa163e5bc304
  677. X-Return-Path-Hint: AbeefbeefbeefbeefbeefUA==_1133104752381_sUQcUKVBEeynm/oWPlvDBA==@in.constantcontact.com
  678. X-Roving-Campaignid: 1140728754811
  679. X-Roving-Id: 1133104752381.1111111111
  680. X-Feedback-ID: b1441c50-a541-11ec-beef-beefbeefbeefbeef5de2a-5c88-44b1-be0e-f5a444cb0650:1133104752381:CTCT
  681. X-CTCT-ID: b13a9586-a541-11ec-beef-beefbeefbeef
  682. ------=_Part_75055660_144854819.1698672187348
  683. Content-Type: text/plain; charset=utf-8
  684. Content-Transfer-Encoding: quoted-printable
  685. When you're buying a home, Pre-Approval gives you confidence you're in the =
  686. right price range and shows sellers you mean business. xxxxxxxxx SELLING or=
  687. BUYING? Call: 844-590-2275 Get Your Homebuying PRE-APPROVAL IN 24-HOURS* G=
  688. et Pre-Approved When you're buying a home, Pre-Approval gives you confidenc=
  689. e you're in the right price range and shows sellers you mean business. xxx=
  690. xxxxxxGet Pre-Approved today! Click or Call to Get Pre-Approved 844-590-227=
  691. 5 Get Pre-Approved nmlsconsumeraccess.org/ *The 24 hour timeframe is for mo=
  692. st approvals, however if additional information is needed or a request is o=
  693. n a holiday, the time for preapproval may be greater than 24 hours. This em=
  694. ail is for informational purposes only and is not an offer, loan approval o=
  695. r loan commitment. Mortgage rates are subject to change without notice. Som=
  696. e terms and restrictions may apply to certain loan programs. Refinancing ex=
  697. isting loans may result in total finance charges being higher over the life=
  698. of the loan, reduction in payments may partially reflect a longer loan ter=
  699. m. This information is provided as guidance and illustrative purposes only =
  700. and does not constitute legal or financial advice. We are not liable or bou=
  701. nd legally for any answers provided to any user for our process or position=
  702. on an issue. This information may change from time to time and at any time=
  703. without notification. The most current information will be updated periodi=
  704. cally and posted in the online forum. spamspam Loan Servicing, LLC. NMLS#39=
  705. 1521. nmlsconsumeraccess.org. You are receiving this information as a curre=
  706. nt loan customer with spamspam Loan Servicing, LLC. Not licensed for lendin=
  707. g activities in any of the U.S. territories. Not authorized to originate lo=
  708. ans in the State of New York. Licensed by the Dept. of Financial Protection=
  709. and Innovation under the California Residential Mortgage .Lending Act #413=
  710. 1216. This email was sent to somebody@gmail.com Version 103023PCHPrAp=
  711. 9 xxxxxxxxx spamspam Loan Servicing | 4425 Ponce de Leon Blvd 5-251, Coral =
  712. Gables, FL 33146-1837 Unsubscribe somebody@gmail.com Update Profile |=
  713. Our Privacy Policy | Constant Contact Data Notice Sent by marklake@spamspa=
  714. m.com
  715. ------=_Part_75055660_144854819.1698672187348
  716. Content-Type: text/html; charset=utf-8
  717. Content-Transfer-Encoding: quoted-printable
  718. <!DOCTYPE HTML>
  719. <html lang=3D"en-US"> <head> <meta http-equiv=3D"Content-Type" content=3D"=
  720. text/html; charset=3Dutf-8"> <meta name=3D"viewport" content=3D"width=3Ddev=
  721. ice-width, initial-scale=3D1, maximum-scale=3D1"> <style type=3D"text/css=
  722. " data-premailer=3D"ignore">=20
  723. @media only screen and (max-width:480px) { .footer-main-width { width: 100%=
  724. !important; } .footer-mobile-hidden { display: none !important; } .foote=
  725. r-mobile-hidden { display: none !important; } .footer-column { display: bl=
  726. ock !important; } .footer-mobile-stack { display: block !important; } .fo=
  727. oter-mobile-stack-padding { padding-top: 3px; } }=20
  728. /* IE: correctly scale images with w/h attbs */ img { -ms-interpolation-mod=
  729. e: bicubic; }=20
  730. .layout { min-width: 100%; }=20
  731. table { table-layout: fixed; } .shell_outer-row { table-layout: auto; }=20
  732. /* Gmail/Web viewport fix */ u + .body .shell_outer-row { width: 620px; }=
  733. =20
  734. /* LIST AND p STYLE OVERRIDES */ .text .text_content-cell p { margin: 0; pa=
  735. dding: 0; margin-bottom: 0; } .text .text_content-cell ul, .text .text_cont=
  736. ent-cell ol { padding: 0; margin: 0 0 0 40px; } .text .text_content-cell li=
  737. { padding: 0; margin: 0; /* line-height: 1.2; Remove after testing */ } /*=
  738. Text Link Style Reset */ a { text-decoration: underline; } /* iOS: Autolin=
  739. k styles inherited */ a[x-apple-data-detectors] { text-decoration: underlin=
  740. e !important; font-size: inherit !important; font-family: inherit !importan=
  741. t; font-weight: inherit !important; line-height: inherit !important; color:=
  742. inherit !important; } /* FF/Chrome: Smooth font rendering */ .text .text_c=
  743. ontent-cell { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing:=
  744. grayscale; }=20
  745. </style> <!--[if gte mso 9]> <style id=3D"ol-styles">=20
  746. /* OUTLOOK-SPECIFIC STYLES */ li { text-indent: -1em; padding: 0; margin: 0=
  747. ; /* line-height: 1.2; Remove after testing */ } ul, ol { padding: 0; margi=
  748. n: 0 0 0 40px; } p { margin: 0; padding: 0; margin-bottom: 0; }=20
  749. </style> <![endif]--> <style>@media only screen and (max-width:480px) {
  750. .button_content-cell {
  751. padding-top: 10px !important; padding-right: 20px !important; padding-botto=
  752. m: 10px !important; padding-left: 20px !important;
  753. }
  754. .button_border-row .button_content-cell {
  755. padding-top: 10px !important; padding-right: 20px !important; padding-botto=
  756. m: 10px !important; padding-left: 20px !important;
  757. }
  758. .column .content-padding-horizontal {
  759. padding-left: 20px !important; padding-right: 20px !important;
  760. }
  761. .layout .column .content-padding-horizontal .content-padding-horizontal {
  762. padding-left: 0px !important; padding-right: 0px !important;
  763. }
  764. .layout .column .content-padding-horizontal .block-wrapper_border-row .cont=
  765. ent-padding-horizontal {
  766. padding-left: 20px !important; padding-right: 20px !important;
  767. }
  768. .dataTable {
  769. overflow: auto !important;
  770. }
  771. .dataTable .dataTable_content {
  772. width: auto !important;
  773. }
  774. .image--mobile-scale .image_container img {
  775. width: auto !important;
  776. }
  777. .image--mobile-center .image_container img {
  778. margin-left: auto !important; margin-right: auto !important;
  779. }
  780. .layout-margin .layout-margin_cell {
  781. padding: 0px 20px !important;
  782. }
  783. .layout-margin--uniform .layout-margin_cell {
  784. padding: 20px 20px !important;
  785. }
  786. .scale {
  787. width: 100% !important;
  788. }
  789. .stack {
  790. display: block !important; box-sizing: border-box;
  791. }
  792. .hide {
  793. display: none !important;
  794. }
  795. u + .body .shell_outer-row {
  796. width: 100% !important;
  797. }
  798. .socialFollow_container {
  799. text-align: center !important;
  800. }
  801. .text .text_content-cell {
  802. font-size: 16px !important;
  803. }
  804. .text .text_content-cell h1 {
  805. font-size: 24px !important;
  806. }
  807. .text .text_content-cell h2 {
  808. font-size: 20px !important;
  809. }
  810. .text .text_content-cell h3 {
  811. font-size: 20px !important;
  812. }
  813. .text--sectionHeading .text_content-cell {
  814. font-size: 26px !important;
  815. }
  816. .text--heading .text_content-cell {
  817. font-size: 26px !important;
  818. }
  819. .text--feature .text_content-cell h2 {
  820. font-size: 20px !important;
  821. }
  822. .text--articleHeading .text_content-cell {
  823. font-size: 20px !important;
  824. }
  825. .text--article .text_content-cell h3 {
  826. font-size: 20px !important;
  827. }
  828. .text--featureHeading .text_content-cell {
  829. font-size: 20px !important;
  830. }
  831. .text--feature .text_content-cell h3 {
  832. font-size: 20px !important;
  833. }
  834. .text--dataTable .text_content-cell .dataTable .dataTable_content-cell {
  835. font-size: 12px !important;
  836. }
  837. .text--dataTable .text_content-cell .dataTable th.dataTable_content-cell {
  838. font-size: px !important;
  839. }
  840. }
  841. </style>
  842. </head> <body class=3D"body template template--en-US" data-template-version=
  843. =3D"1.38.0" data-canonical-name=3D"CPE10001" lang=3D"en-US" align=3D"center=
  844. " style=3D"-ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; min-=
  845. width: 100%; width: 100%; margin: 0px; padding: 0px;"> <div id=3D"preheader=
  846. " style=3D"color: transparent; display: none; font-size: 1px; line-height: =
  847. 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden;"><span =
  848. data-entity-ref=3D"preheader">When you&#x27;re buying a home, Pre-Approval =
  849. gives you confidence you&#x27;re in the right price range and shows sellers=
  850. you mean business. </span></div> <div id=3D"tracking-image" style=3D"color=
  851. : transparent; display: none; font-size: 1px; line-height: 1px; max-height:=
  852. 0px; max-width: 0px; opacity: 0; overflow: hidden;"><img src=3D"https://r2=
  853. 0.rs6.net/on.jsp?ca=beefbeefbe-beef-44b1-be0e-f5a444cb0650&a=3D113310475238=
  854. 1&c=3Db13a9586-a541-11ec-a79b-fa163e5bc304&ch=3Db1441c50-a541-11ec-a79b-fa1=
  855. 63e5bc304" / alt=3D""></div> <div class=3D"shell" lang=3D"en-US" style=3D"b=
  856. ackground-color: #015288;"> <table class=3D"shell_panel-row" width=3D"100%=
  857. " border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"background-colo=
  858. r: #015288;" bgcolor=3D"#015288"> <tr class=3D""> <td class=3D"shell_panel-=
  859. cell" style=3D"" align=3D"center" valign=3D"top"> <table class=3D"shell_wid=
  860. th-row scale" style=3D"width: 620px;" align=3D"center" border=3D"0" cellpad=
  861. ding=3D"0" cellspacing=3D"0"> <tr> <td class=3D"shell_width-cell" style=3D"=
  862. padding: 15px 10px;" align=3D"center" valign=3D"top"> <table class=3D"shell=
  863. _content-row" width=3D"100%" align=3D"center" border=3D"0" cellpadding=3D"0=
  864. " cellspacing=3D"0"> <tr> <td class=3D"shell_content-cell" style=3D"border-=
  865. radius: 0px; background-color: #FFFFFF; padding: 0; border: 0px solid #0096=
  866. d6;" align=3D"center" valign=3D"top" bgcolor=3D"#FFFFFF"> <table class=3D"l=
  867. ayout layout--1-column" style=3D"table-layout: fixed;" width=3D"100%" borde=
  868. r=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column colum=
  869. n--1 scale stack" style=3D"width: 100%;" align=3D"center" valign=3D"top">
  870. <table class=3D"divider" width=3D"100%" cellpadding=3D"0" cellspacing=3D"0"=
  871. border=3D"0"> <tr> <td class=3D"divider_container" style=3D"padding-top: 0=
  872. px; padding-bottom: 10px;" width=3D"100%" align=3D"center" valign=3D"top"> =
  873. <table class=3D"divider_content-row" style=3D"width: 100%; height: 1px;" ce=
  874. llpadding=3D"0" cellspacing=3D"0" border=3D"0"> <tr> <td class=3D"divider_c=
  875. ontent-cell" style=3D"padding-bottom: 5px; height: 1px; line-height: 1px; b=
  876. ackground-color: #0096D6; border-bottom-width: 0px;" height=3D"1" align=3D"=
  877. center" bgcolor=3D"#0096D6"> <img alt=3D"" width=3D"5" height=3D"1" border=
  878. =3D"0" hspace=3D"0" vspace=3D"0" src=3D"https://imgssl.constantcontact.com/=
  879. letters/images/1101116784221/S.gif" style=3D"display: block; height: 1px; w=
  880. idth: 5px;"> </td> </tr> </table> </td> </tr> </table> </td> </tr> </table>=
  881. <table class=3D"layout layout--1-column" style=3D"table-layout: fixed;" wi=
  882. dth=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td cla=
  883. ss=3D"column column--1 scale stack" style=3D"width: 100%;" align=3D"center"=
  884. valign=3D"top"><div class=3D"spacer" style=3D"line-height: 10px; height: 1=
  885. 0px;">&#x200a;</div></td> </tr> </table> <table class=3D"layout layout--1-c=
  886. olumn" style=3D"table-layout: fixed;" width=3D"100%" border=3D"0" cellpaddi=
  887. ng=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column column--1 scale stack"=
  888. style=3D"width: 100%;" align=3D"center" valign=3D"top">
  889. <table class=3D"image image--padding-vertical image--mobile-scale image--mo=
  890. bile-center" width=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0=
  891. "> <tr> <td class=3D"image_container" align=3D"center" valign=3D"top" style=
  892. =3D"padding-top: 10px; padding-bottom: 10px;"> <a href=3D"https://r20.rs6.n=
  893. et/tn.jsp?f=3D001YKO1VR2jLW0SuSLZLfN7qCP9AwEGO0v-Vy-0SCUlMWvTEiCsv-QEMhmJe9=
  894. ch=3DHu9wLy0fth6D8jxFBWPA_NhdnWcZZPivk0KUTgRJoVIo_si10jiydw=3D=3D" data-tra=
  895. ckable=3D"true"><img data-image-content class=3D"image_content" width=3D"26=
  896. 2" src=3D"https://files.constantcontact.com/beefbeefbee/057bff2a-bdba-4165-=
  897. b108-a7baa91c42c6.jpg" alt=3D"" style=3D"display: block; height: auto; max-=
  898. width: 100%;"></a> </td> </tr> </table> </td> </tr> </table> <table class=
  899. =3D"layout layout--heading layout--1-column" style=3D"background-color: #00=
  900. 527e; table-layout: fixed;" width=3D"100%" border=3D"0" cellpadding=3D"0" c=
  901. ellspacing=3D"0" bgcolor=3D"#00527e"> <tr> <td class=3D"column column--1 sc=
  902. ale stack" style=3D"width: 100%;" align=3D"center" valign=3D"top">
  903. <table class=3D"text text--padding-vertical" width=3D"100%" border=3D"0" ce=
  904. llpadding=3D"0" cellspacing=3D"0" style=3D"table-layout: fixed;"> <tr> <td =
  905. class=3D"text_content-cell content-padding-horizontal" style=3D"text-align:=
  906. center; font-family: Arial,Verdana,Helvetica,sans-serif; color: #000000; f=
  907. ont-size: 14px; line-height: 1.2; display: block; word-wrap: break-word; pa=
  908. dding: 10px 20px;" align=3D"center" valign=3D"top">
  909. <h1 style=3D"font-family: Arial,Verdana,Helvetica,sans-serif; color: #606d7=
  910. 8; font-size: 26px; font-weight: bold; margin: 0;"><span style=3D"color: rg=
  911. b(0, 150, 214);">SELLING or BUYING?</span></h1>
  912. <p style=3D"margin: 0;"><span style=3D"font-size: 16px; color: rgb(255, 255=
  913. , 255); font-weight: bold;">Call: 844-590-2275</span></p>
  914. </td> </tr> </table> </td> </tr> </table> <table class=3D"layout layout--ar=
  915. ticle layout--1-column" style=3D"table-layout: fixed;" width=3D"100%" borde=
  916. r=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column colum=
  917. n--1 scale stack" style=3D"width: 100%;" align=3D"center" valign=3D"top">
  918. <table class=3D"text text--heading text--padding-vertical" width=3D"100%" b=
  919. order=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"table-layout: fixe=
  920. d;"> <tr> <td class=3D"text_content-cell content-padding-horizontal" style=
  921. =3D"text-align: center; font-family: Arial,Verdana,Helvetica,sans-serif; co=
  922. lor: #606d78; font-size: 26px; line-height: 1.2; display: block; word-wrap:=
  923. break-word; font-weight: bold; padding: 10px 20px;" align=3D"center" valig=
  924. n=3D"top">
  925. <p style=3D"margin: 0;"><span style=3D"font-size: 30px; color: rgb(0, 150, =
  926. 214);">Get Your Homebuying</span></p>
  927. <p style=3D"margin: 0;"><span style=3D"font-size: 30px; color: rgb(0, 82, 1=
  928. 26);">PRE-APPROVAL IN 24-HOURS</span><span style=3D"font-size: 30px; color:=
  929. rgb(0, 82, 126); font-weight: normal;">*</span></p>
  930. </td> </tr> </table> <table class=3D"image image--padding-vertical image--m=
  931. obile-scale image--mobile-center" width=3D"100%" border=3D"0" cellpadding=
  932. =3D"0" cellspacing=3D"0"> <tr> <td class=3D"image_container content-padding=
  933. -horizontal" align=3D"center" valign=3D"top" style=3D"padding: 10px 20px;">=
  934. <img data-image-content class=3D"image_content" width=3D"548" src=3D"https=
  935. ://files.constantcontact.com/df66e42d701/2092a2d7-0bda-4289-910b-bf50a2398d=
  936. 60.jpg" alt=3D"" style=3D"display: block; height: auto; max-width: 100%;"> =
  937. </td> </tr> </table> <table class=3D"button button--padding-vertical" widt=
  938. h=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" style=3D"table-=
  939. layout: fixed;"> <tr> <td class=3D"button_container content-padding-horizon=
  940. tal" align=3D"center" style=3D"padding: 10px 20px;"> <table class=3D"but=
  941. ton_content-row" style=3D"width: inherit; border-radius: 3px; border-spacin=
  942. g: 0; background-color: #0096D6; border: none;" border=3D"0" cellpadding=3D=
  943. "0" cellspacing=3D"0" bgcolor=3D"#0096D6"> <tr> <td class=3D"button_content=
  944. -cell" style=3D"padding: 10px 40px;" align=3D"center"> <a class=3D"button_l=
  945. ink" href=3D"https://r20.rs6.net/tn.jsp?f=3D001YKO1VR2jLW0SuSLZLfN7qCP9AwEG=
  946. O0v-Vy-0SCUlMWvTEiCsv-QEMuu9ZVVi6WGHhCias4f7-QkeggQvxIvbs-6TTaZHHhXLKf88NID=
  947. dci4Ge7aYN-QihEgqblie1-DQ2Fa1BKLbT3AM8rtrgeYQgVxJ6cG8POsvFzv7JstrGkCkg3a3AE=
  948. 633LfQpAddyVLFkTv6oyS4T2j_YjYIPKDOZktqK_5rOR-Fh8cWGtUD8YPpPNnZ037z6_t9Nkemu=
  949. hxG&c=3DA65qX-dQJPS0J4afCS7H0Je5N-_6Q8Nh2fNHkb5-5biUYd5B9SY3zA=3D=3D&ch=3DH=
  950. u9wLy0fth6D8jxFBWPA_NhdnWcZZPivk0KUTgRJoVIo_si10jiydw=3D=3D" data-trackable=
  951. =3D"true" style=3D"font-size: 16px; font-weight: bold; color: #FFFFFF; font=
  952. -family: Helvetica,Arial,sans-serif; word-wrap: break-word; text-decoration=
  953. : none;">Get Pre-Approved</a> </td> </tr> </table> </td> </tr> </table> =
  954. <table class=3D"text text--padding-vertical" width=3D"100%" border=3D"0" =
  955. cellpadding=3D"0" cellspacing=3D"0" style=3D"table-layout: fixed;"> <tr> <t=
  956. d class=3D"text_content-cell content-padding-horizontal" style=3D"line-heig=
  957. ht: 1; text-align: center; font-family: Arial,Verdana,Helvetica,sans-serif;=
  958. color: #000000; font-size: 14px; display: block; word-wrap: break-word; pa=
  959. dding: 10px 20px;" align=3D"center" valign=3D"top">
  960. <p style=3D"text-align: left; margin: 0;" align=3D"left"><br></p>
  961. <p style=3D"margin: 0;"><span style=3D"font-size: 19px;">When you're buying=
  962. a home, Pre-Approval gives you confidence you're in the right price range =
  963. and shows sellers you mean business. </span></p>
  964. <p style=3D"margin: 0;"><span style=3D"font-size: 19px;">&#xfeff;Get Pre-Ap=
  965. proved today!</span></p>
  966. </td> </tr> </table> </td> </tr> </table> <table class=3D"layout layout--1-=
  967. column" style=3D"table-layout: fixed;" width=3D"100%" border=3D"0" cellpadd=
  968. ing=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column column--1 scale stack=
  969. " style=3D"width: 100%;" align=3D"center" valign=3D"top">
  970. <table class=3D"text text--padding-vertical" width=3D"100%" border=3D"0" ce=
  971. llpadding=3D"0" cellspacing=3D"0" style=3D"table-layout: fixed;"> <tr> <td =
  972. class=3D"text_content-cell content-padding-horizontal" style=3D"text-align:=
  973. left; font-family: Arial,Verdana,Helvetica,sans-serif; color: #000000; fon=
  974. t-size: 14px; line-height: 1.2; display: block; word-wrap: break-word; padd=
  975. ing: 10px 20px;" align=3D"left" valign=3D"top">
  976. <p style=3D"text-align: center; margin: 0;" align=3D"center"><br></p>
  977. <p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
  978. "font-size: 23px; color: rgb(0, 82, 126); font-weight: bold; font-family: A=
  979. rial, Verdana, Helvetica, sans-serif;">Click or Call to Get Pre-Approved </=
  980. span></p>
  981. <p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
  982. "font-size: 28px; color: rgb(0, 150, 214); font-weight: bold;">844-590-2275=
  983. </span></p>
  984. </td> </tr> </table> </td> </tr> </table> <table class=3D"layout layout--1-=
  985. column" style=3D"table-layout: fixed;" width=3D"100%" border=3D"0" cellpadd=
  986. ing=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column column--1 scale stack=
  987. " style=3D"width: 100%;" align=3D"center" valign=3D"top"> <table class=3D"b=
  988. utton button--padding-vertical" width=3D"100%" border=3D"0" cellpadding=3D"=
  989. 0" cellspacing=3D"0" style=3D"table-layout: fixed;"> <tr> <td class=3D"butt=
  990. on_container content-padding-horizontal" align=3D"center" style=3D"padding:=
  991. 10px 20px;"> <table class=3D"button_content-row" style=3D"background-co=
  992. lor: #0096D6; width: inherit; border-radius: 3px; border-spacing: 0; border=
  993. : none;" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" bgcolor=3D"#0096D=
  994. 6"> <tr> <td class=3D"button_content-cell" style=3D"padding: 10px 40px;" al=
  995. ign=3D"center"> <a class=3D"button_link" href=3D"https://r20.rs6.net/tn.jsp=
  996. ?f=3D001thisisfakethisisfakethisisfakev-Vy-0SCUlMWvTEiCsv-QEMuu9ZVVi6WGHhCi=
  997. oVIo_si10jiydw=3D=3D" data-trackable=3D"true" style=3D"font-size: 16px; fon=
  998. t-weight: bold; color: #FFFFFF; font-family: Helvetica,Arial,sans-serif; wo=
  999. rd-wrap: break-word; text-decoration: none;">Get Pre-Approved</a> </td> </t=
  1000. r> </table> </td> </tr> </table> </td> </tr> </table> <table class=3D"=
  1001. layout layout--1-column" style=3D"table-layout: fixed;" width=3D"100%" bord=
  1002. er=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column colu=
  1003. mn--1 scale stack" style=3D"width: 100%;" align=3D"center" valign=3D"top">
  1004. <table class=3D"image image--padding-vertical image--mobile-scale image--mo=
  1005. bile-center" width=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0=
  1006. "> <tr> <td class=3D"image_container" align=3D"center" valign=3D"top" style=
  1007. =3D"padding-top: 10px; padding-bottom: 10px;"> <img data-image-content clas=
  1008. s=3D"image_content" width=3D"87" src=3D"https://files.constantcontact.com/d=
  1009. f66e42d701/beefbeef-beef-beef-9a13-2779ab497b8d.png" alt=3D"" style=3D"disp=
  1010. lay: block; height: auto; max-width: 100%;"> </td> </tr> </table> </td> </t=
  1011. r> </table> <table class=3D"layout layout--1-column" style=3D"table-layout:=
  1012. fixed;" width=3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <=
  1013. tr> <td class=3D"column column--1 scale stack" style=3D"width: 100%;" align=
  1014. =3D"center" valign=3D"top">
  1015. <table class=3D"text text--padding-vertical" width=3D"100%" border=3D"0" ce=
  1016. llpadding=3D"0" cellspacing=3D"0" style=3D"table-layout: fixed;"> <tr> <td =
  1017. class=3D"text_content-cell content-padding-horizontal" style=3D"text-align:=
  1018. left; font-family: Arial,Verdana,Helvetica,sans-serif; color: #000000; fon=
  1019. t-size: 14px; line-height: 1.2; display: block; word-wrap: break-word; padd=
  1020. ing: 10px 20px;" align=3D"left" valign=3D"top">
  1021. <p style=3D"text-align: center; margin: 0;" align=3D"center"><br></p>
  1022. <p style=3D"text-align: center; margin: 0;" align=3D"center"><a href=3D"htt=
  1023. ps://r20.rs6.net/tn.jsp?f=3D001YKO1VR2jLW0SuSLZLfN7qCP9AwEGO0v-Vy-0SCUlMWvT=
  1024. EiCsv-QEMgYju54LKeEV1_a2OCyOAfG7VhZpxtOW89WM-s6S5iiXcmnbK-Z6XDc9LL569h6DE4L=
  1025. IRMWiBWHOlFB9TZWQVuX6Ycz3505y1keCrca4QArp&c=3DA65qX-dQJPS0J4afCS7H0Je5N-_6Q=
  1026. 8Nh2fNHkb5-5biUYd5B9SY3zA=3D=3D&ch=3DHu9wLy0fth6D8jxFBWPA_NhdnWcZZPivk0KUTg=
  1027. RJoVIo_si10jiydw=3D=3D" target=3D"_blank" style=3D"font-size: 11px; color: =
  1028. rgb(153, 153, 153); text-decoration: underline; font-weight: normal; font-s=
  1029. tyle: normal;">nmlsconsumeraccess.org/</a></p>
  1030. <p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
  1031. "font-size: 11px; color: rgb(153, 153, 153);">*The 24 hour timeframe is for=
  1032. most approvals, however if additional information is needed or a request i=
  1033. s on a holiday, the time for preapproval may be greater than 24 hours.</spa=
  1034. n></p>
  1035. <p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
  1036. "font-size: 11px; color: rgb(153, 153, 153); background-color: rgb(255, 255=
  1037. , 255);">This email is for informational purposes only and is not an offer,=
  1038. loan approval or loan commitment. Mortgage rates are subject to change wit=
  1039. hout notice. Some terms and restrictions may apply to certain loan programs=
  1040. . Refinancing existing loans may result in total finance charges being high=
  1041. er over the life of the loan, reduction in payments may partially reflect a=
  1042. longer loan term. This information is provided as guidance and illustrativ=
  1043. e purposes only and does not constitute legal or financial advice. We are n=
  1044. ot liable or bound legally for any answers provided to any user for our pro=
  1045. cess or position on an issue. This information may change from time to time=
  1046. and at any time without notification. The most current information will be=
  1047. updated periodically and posted in the online forum.</span></p>
  1048. <p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
  1049. "font-size: 11px; color: rgb(153, 153, 153); background-color: rgb(255, 255=
  1050. , 255);">spamspam Loan Servicing, LLC. NMLS#391521. nmlsconsumeraccess.org.=
  1051. You are receiving this information as a current loan customer with spamspa=
  1052. m Loan Servicing, LLC. Not licensed for lending activities in any of the U.=
  1053. S. territories. Not authorized to originate loans in the State of New York.=
  1054. Licensed by the Dept. of Financial Protection and Innovation under the Cal=
  1055. ifornia Residential Mortgage .Lending Act #4131216.</span></p>
  1056. <p style=3D"text-align: center; margin: 0;" align=3D"center"><br></p>
  1057. <p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
  1058. "font-size: 11px; color: rgb(153, 153, 153);">This email was sent to <span =
  1059. data-id=3D"emailAddress">somebody@gmail.com</span></span></p>
  1060. <p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
  1061. "font-size: 11px; color: rgb(153, 153, 153);">Version 103023PCHPrAp9 </span=
  1062. ></p>
  1063. <p style=3D"text-align: center; margin: 0;" align=3D"center"><span style=3D=
  1064. "font-size: 11px; color: rgb(162, 162, 162);">&#xfeff;</span></p>
  1065. </td> </tr> </table> </td> </tr> </table> <table class=3D"layout layout--1-=
  1066. column" style=3D"table-layout: fixed;" width=3D"100%" border=3D"0" cellpadd=
  1067. ing=3D"0" cellspacing=3D"0"> <tr> <td class=3D"column column--1 scale stack=
  1068. " style=3D"width: 100%;" align=3D"center" valign=3D"top">
  1069. <table class=3D"divider" width=3D"100%" cellpadding=3D"0" cellspacing=3D"0"=
  1070. border=3D"0"> <tr> <td class=3D"divider_container" style=3D"padding-top: 1=
  1071. 0px; padding-bottom: 0px;" width=3D"100%" align=3D"center" valign=3D"top"> =
  1072. <table class=3D"divider_content-row" style=3D"width: 100%; height: 1px;" ce=
  1073. llpadding=3D"0" cellspacing=3D"0" border=3D"0"> <tr> <td class=3D"divider_c=
  1074. ontent-cell" style=3D"padding-bottom: 2px; height: 1px; line-height: 1px; b=
  1075. ackground-color: #0096D6; border-bottom-width: 0px;" height=3D"1" align=3D"=
  1076. center" bgcolor=3D"#0096D6"> <img alt=3D"" width=3D"5" height=3D"1" border=
  1077. =3D"0" hspace=3D"0" vspace=3D"0" src=3D"https://imgssl.constantcontact.com/=
  1078. letters/images/1111111111111/S.gif" style=3D"display: block; height: 1px; w=
  1079. idth: 5px;"> </td> </tr> </table> </td> </tr> </table> </td> </tr> </table>=
  1080. </td> </tr> </table> </td> </tr> </table> </td> </tr> <tr> <td class=3D"s=
  1081. hell_panel-cell shell_panel-cell--systemFooter" style=3D"" align=3D"center"=
  1082. valign=3D"top"> <table class=3D"shell_width-row scale" style=3D"width: 100=
  1083. %;" align=3D"center" border=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr>=
  1084. <td class=3D"shell_width-cell" style=3D"padding: 0px;" align=3D"center" va=
  1085. lign=3D"top"> <table class=3D"shell_content-row" width=3D"100%" align=3D"ce=
  1086. nter" border=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td class=3D"s=
  1087. hell_content-cell" style=3D"background-color: #FFFFFF; padding: 0; border: =
  1088. 0 solid #0096d6;" align=3D"center" valign=3D"top" bgcolor=3D"#FFFFFF"> <tab=
  1089. le class=3D"layout layout--1-column" style=3D"table-layout: fixed;" width=
  1090. =3D"100%" border=3D"0" cellpadding=3D"0" cellspacing=3D"0"> <tr> <td class=
  1091. =3D"column column--1 scale stack" style=3D"width: 100%;" align=3D"center" v=
  1092. align=3D"top"> <table class=3D"footer" width=3D"100%" border=3D"0" cellpadd=
  1093. ing=3D"0" cellspacing=3D"0" style=3D"font-family: Verdana,Geneva,sans-serif=
  1094. ; color: #5d5d5d; font-size: 12px;"> <tr> <td class=3D"footer_container" al=
  1095. ign=3D"center"> <table class=3D"footer-container" width=3D"100%" cellpaddin=
  1096. g=3D"0" cellspacing=3D"0" border=3D"0" style=3D"background-color: #ffffff; =
  1097. margin-left: auto; margin-right: auto; table-layout: auto !important;" bgco=
  1098. lor=3D"#ffffff">
  1099. <tr>
  1100. <td width=3D"100%" align=3D"center" valign=3D"top" style=3D"width: 100%;">
  1101. <div class=3D"footer-max-main-width" align=3D"center" style=3D"margin-left:=
  1102. auto; margin-right: auto; max-width: 100%;">
  1103. <table width=3D"100%" cellpadding=3D"0" cellspacing=3D"0" border=3D"0">
  1104. <tr>
  1105. <td class=3D"footer-layout" align=3D"center" valign=3D"top" style=3D"paddin=
  1106. g: 16px 0px;">
  1107. <table class=3D"footer-main-width" style=3D"width: 580px;" border=3D"0" cel=
  1108. lpadding=3D"0" cellspacing=3D"0">
  1109. <tr>
  1110. <td class=3D"footer-text" align=3D"center" valign=3D"top" style=3D"color: #=
  1111. 5d5d5d; font-family: Verdana,Geneva,sans-serif; font-size: 12px; padding: 4=
  1112. px 0px;">
  1113. <span class=3D"footer-column">spamspam Loan Servicing<span class=3D"footer-=
  1114. mobile-hidden"> | </span></span><span class=3D"footer-column">4425 Ponce de=
  1115. Leon Blvd 5-251<span class=3D"footer-mobile-hidden">, </span></span><span =
  1116. class=3D"footer-column"></span><span class=3D"footer-column"></span><span c=
  1117. lass=3D"footer-column">Coral Gables, FL 33146-1837</span><span class=3D"foo=
  1118. ter-column"></span>
  1119. </td>
  1120. </tr>
  1121. <tr>
  1122. <td class=3D"footer-row" align=3D"center" valign=3D"top" style=3D"padding: =
  1123. 10px 0px;">
  1124. <table cellpadding=3D"0" cellspacing=3D"0" border=3D"0">
  1125. <tr>
  1126. <td class=3D"footer-text" align=3D"center" valign=3D"top" style=3D"color: #=
  1127. 5d5d5d; font-family: Verdana,Geneva,sans-serif; font-size: 12px; padding: 4=
  1128. px 0px;">
  1129. <a href=3D"https://visitor.constantcontact.com/do?p=3Dun&m=3D001g3dtlqhzM3v=
  1130. -44b1-be0e-f5a444cb0650" data-track=3D"false" style=3D"color: #5d5d5d;">Uns=
  1131. ubscribe somebody@gmail.com<span class=3D"partnerOptOut"></span></a>
  1132. <span class=3D"partnerOptOut"></span>
  1133. </td>
  1134. </tr>
  1135. <tr>
  1136. <td class=3D"footer-text" align=3D"center" valign=3D"top" style=3D"color: #=
  1137. 5d5d5d; font-family: Verdana,Geneva,sans-serif; font-size: 12px; padding: 4=
  1138. px 0px;">
  1139. <a href=3D"https://visitor.constantcontact.com/do?p=3Doo&m=3D001g3dtlqhzM3v=
  1140. -44b1-be0e-f5a444cb0650" data-track=3D"false" style=3D"color: #5d5d5d;">Upd=
  1141. ate Profile</a> |
  1142. <a href=3D"https://spamspam.com/privacy-notice/" data-track=3D"false" style=
  1143. =3D"color: #5d5d5d;">Our Privacy Policy</a><span class=3D"footer-mobile-hid=
  1144. den"> |</span>
  1145. <a class=3D"footer-about-provider footer-mobile-stack footer-mobile-stack-p=
  1146. adding" href=3D"http://www.constantcontact.com/legal/about-constant-contact=
  1147. " data-track=3D"false" style=3D"color: #5d5d5d;">Constant Contact Data Noti=
  1148. ce</a>
  1149. </td>
  1150. </tr>
  1151. <tr>
  1152. <td class=3D"footer-text" align=3D"center" valign=3D"top" style=3D"color: #=
  1153. 5d5d5d; font-family: Verdana,Geneva,sans-serif; font-size: 12px; padding: 4=
  1154. px 0px;">
  1155. Sent by
  1156. <a href=3D"mailto:marklake@spamspam.com" style=3D"color: #5d5d5d; text-deco=
  1157. ration: none;">marklake@spamspam.com</a>
  1158. </td>
  1159. </tr>
  1160. </table>
  1161. </td>
  1162. </tr>
  1163. <tr>
  1164. <td class=3D"footer-text" align=3D"center" valign=3D"top" style=3D"color: #=
  1165. 5d5d5d; font-family: Verdana,Geneva,sans-serif; font-size: 12px; padding: 4=
  1166. px 0px;">
  1167. </td>
  1168. </tr>
  1169. </table>
  1170. </td>
  1171. </tr>
  1172. </table>
  1173. </div>
  1174. </td>
  1175. </tr>
  1176. </table> </td> </tr> </table> </td> </tr> </table> </td> </tr> </table> =
  1177. </td> </tr> </table> </td> </tr> </table> </div> </body> </html>
  1178. ------=_Part_75055660_144854819.1698672187348--
  1179. .
  1180. `
  1181. func TestSmtpBackend_Spam_Text(t *testing.T) {
  1182. email := spamEmail
  1183. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  1184. require.Equal(t, "/mytopic", r.URL.Path)
  1185. require.Equal(t, "Buying a home? You deserve the confidence of Pre-Approval", r.Header.Get("Title"))
  1186. actual := readAll(t, r.Body)
  1187. expected := "When you're buying a home, Pre-Approval gives you confidence you're in the right price range and shows sellers you mean business. xxxxxxxxx SELLING or BUYING? Call: 844-590-2275 Get Your Homebuying PRE-APPROVAL IN 24-HOURS* Get Pre-Approved When you're buying a home, Pre-Approval gives you confidence you're in the right price range and shows sellers you mean business. xxxxxxxxxGet Pre-Approved today! Click or Call to Get Pre-Approved 844-590-2275 Get Pre-Approved nmlsconsumeraccess.org/ *The 24 hour timeframe is for most approvals, however if additional information is needed or a request is on a holiday, the time for preapproval may be greater than 24 hours. This email is for informational purposes only and is not an offer, loan approval or loan commitment. Mortgage rates are subject to change without notice. Some terms and restrictions may apply to certain loan programs. Refinancing existing loans may result in total finance charges being higher over the life of the loan, reduction in payments may partially reflect a longer loan term. This information is provided as guidance and illustrative purposes only and does not constitute legal or financial advice. We are not liable or bound legally for any answers provided to any user for our process or position on an issue. This information may change from time to time and at any time without notification. The most current information will be updated periodically and posted in the online forum. spamspam Loan Servicing, LLC. NMLS#391521. nmlsconsumeraccess.org. You are receiving this information as a current loan customer with spamspam Loan Servicing, LLC. Not licensed for lending activities in any of the U.S. territories. Not authorized to originate loans in the State of New York. Licensed by the Dept. of Financial Protection and Innovation under the California Residential Mortgage .Lending Act #4131216. This email was sent to somebody@gmail.com Version 103023PCHPrAp9 xxxxxxxxx spamspam Loan Servicing | 4425 Ponce de Leon Blvd 5-251, Coral Gables, FL 33146-1837 Unsubscribe somebody@gmail.com Update Profile | Our Privacy Policy | Constant Contact Data Notice Sent by marklake@spamspam.com"
  1188. require.Equal(t, expected, actual)
  1189. })
  1190. defer s.Close()
  1191. defer c.Close()
  1192. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  1193. }
  1194. func TestSmtpBackend_Spam_HTML(t *testing.T) {
  1195. email := strings.ReplaceAll(spamEmail, "text/plain", "text/not-plain-anymore") // We artificially force HTML parsing here
  1196. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  1197. require.Equal(t, "/mytopic", r.URL.Path)
  1198. require.Equal(t, "Buying a home? You deserve the confidence of Pre-Approval", r.Header.Get("Title"))
  1199. actual := readAll(t, r.Body)
  1200. expected := `When you&#39;re buying a home, Pre-Approval gives you confidence you&#39;re in the right price range and shows sellers you mean business.
  1201. ` + "\u200a" + `
  1202. SELLING or BUYING?
  1203. Call: 844-590-2275
  1204. Get Your Homebuying
  1205. PRE-APPROVAL IN 24-HOURS *
  1206. Get Pre-Approved
  1207. When you&#39;re buying a home, Pre-Approval gives you confidence you&#39;re in the right price range and shows sellers you mean business.
  1208. ` + "\ufeff" + `Get Pre-Approved today!
  1209. Click or Call to Get Pre-Approved
  1210. 844-590-2275
  1211. Get Pre-Approved
  1212. nmlsconsumeraccess.org/
  1213. *The 24 hour timeframe is for most approvals, however if additional information is needed or a request is on a holiday, the time for preapproval may be greater than 24 hours.
  1214. This email is for informational purposes only and is not an offer, loan approval or loan commitment. Mortgage rates are subject to change without notice. Some terms and restrictions may apply to certain loan programs Refinancing existing loans may result in total finance charges being higher over the life of the loan, reduction in payments may partially reflect a longer loan term. This information is provided as guidance and illustrative purposes only and does not constitute legal or financial advice. We are not liable or bound legally for any answers provided to any user for our process or position on an issue. This information may change from time to time and at any time without notification. The most current information will be updated periodically and posted in the online forum.
  1215. spamspam Loan Servicing, LLC. NMLS#391521. nmlsconsumeraccess.org. You are receiving this information as a current loan customer with spamspam Loan Servicing, LLC. Not licensed for lending activities in any of the U.S. territories. Not authorized to originate loans in the State of New York. Licensed by the Dept. of Financial Protection and Innovation under the California Residential Mortgage .Lending Act #4131216.
  1216. This email was sent to somebody@gmail.com
  1217. Version 103023PCHPrAp9
  1218. ` + "\ufeff" + `
  1219. spamspam Loan Servicing | 4425 Ponce de Leon Blvd 5-251 , Coral Gables, FL 33146-1837
  1220. Unsubscribe somebody@gmail.com
  1221. Update Profile |
  1222. Our Privacy Policy |
  1223. Constant Contact Data Notice
  1224. Sent by
  1225. marklake@spamspam.com`
  1226. require.Equal(t, expected, actual)
  1227. })
  1228. defer s.Close()
  1229. defer c.Close()
  1230. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  1231. }
  1232. func TestSmtpBackend_HTMLOnly_FromDiskStation(t *testing.T) {
  1233. email := `EHLO example.com
  1234. MAIL FROM: synology@mydomain.me
  1235. RCPT TO: synology@mydomain.me
  1236. DATA
  1237. From: "=?UTF-8?B?Um9iYmll?=" <synology@mydomain.me>
  1238. To: <synology@mydomain.me>
  1239. Message-Id: <640e6f562895d.6c9584bcfa491ac9c546b480b32ffc1d@mydomain.me>
  1240. MIME-Version: 1.0
  1241. Subject: =?UTF-8?B?W1N5bm9sb2d5IE5BU10gVGVzdCBNZXNzYWdlIGZyb20gTGl0dHNfTkFT?=
  1242. Content-Type: text/html; charset=utf-8
  1243. Content-Transfer-Encoding: 8bit
  1244. Congratulations! You have successfully set up the email notification on Synology_NAS.<BR>For further system configurations, please visit http://192.168.1.28:5000/, http://172.16.60.5:5000/.<BR>(If you cannot connect to the server, please contact the administrator.)<BR><BR>From Synology_NAS<BR><BR><BR>
  1245. .
  1246. `
  1247. s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  1248. require.Equal(t, "/synology", r.URL.Path)
  1249. require.Equal(t, "[Synology NAS] Test Message from Litts_NAS", r.Header.Get("Title"))
  1250. actual := readAll(t, r.Body)
  1251. expected := `Congratulations! You have successfully set up the email notification on Synology_NAS. For further system configurations, please visit http://192.168.1.28:5000/, http://172.16.60.5:5000/. (If you cannot connect to the server, please contact the administrator.) From Synology_NAS`
  1252. require.Equal(t, expected, actual)
  1253. })
  1254. conf.SMTPServerDomain = "mydomain.me"
  1255. conf.SMTPServerAddrPrefix = ""
  1256. defer s.Close()
  1257. defer c.Close()
  1258. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  1259. }
  1260. func TestSmtpBackend_PlaintextWithToken(t *testing.T) {
  1261. email := `EHLO example.com
  1262. MAIL FROM: phil@example.com
  1263. RCPT TO: ntfy-mytopic+tk_KLORUqSqvNRLpY11DfkHVbHu9NGG2@ntfy.sh
  1264. DATA
  1265. Subject: Very short mail
  1266. what's up
  1267. .
  1268. `
  1269. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  1270. require.Equal(t, "/mytopic", r.URL.Path)
  1271. require.Equal(t, "Very short mail", r.Header.Get("Title"))
  1272. require.Equal(t, "Bearer tk_KLORUqSqvNRLpY11DfkHVbHu9NGG2", r.Header.Get("Authorization"))
  1273. require.Equal(t, "what's up", readAll(t, r.Body))
  1274. })
  1275. defer s.Close()
  1276. defer c.Close()
  1277. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  1278. }
  1279. func TestSmtpBackend_PlaintextWithPlainAuth(t *testing.T) {
  1280. email := `EHLO example.com
  1281. AUTH PLAIN dGVzdAB0ZXN0ADEyMzQ=
  1282. MAIL FROM: phil@example.com
  1283. RCPT TO: ntfy-mytopic@ntfy.sh
  1284. DATA
  1285. Subject: Very short mail
  1286. what's up
  1287. .
  1288. `
  1289. s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
  1290. require.Equal(t, "/mytopic", r.URL.Path)
  1291. require.Equal(t, "Very short mail", r.Header.Get("Title"))
  1292. require.Equal(t, "Basic dGVzdDoxMjM0", r.Header.Get("Authorization"))
  1293. require.Equal(t, "what's up", readAll(t, r.Body))
  1294. })
  1295. defer s.Close()
  1296. defer c.Close()
  1297. writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
  1298. }
  1299. type smtpHandlerFunc func(http.ResponseWriter, *http.Request)
  1300. func newTestSMTPServer(t *testing.T, handler smtpHandlerFunc) (s *smtp.Server, c net.Conn, conf *Config, scanner *bufio.Scanner) {
  1301. conf = newTestConfig(t)
  1302. conf.SMTPServerListen = ":25"
  1303. conf.SMTPServerDomain = "ntfy.sh"
  1304. conf.SMTPServerAddrPrefix = "ntfy-"
  1305. backend := newMailBackend(conf, handler)
  1306. l, err := net.Listen("tcp", "127.0.0.1:0")
  1307. if err != nil {
  1308. t.Fatal(err)
  1309. }
  1310. s = smtp.NewServer(backend)
  1311. s.Domain = conf.SMTPServerDomain
  1312. s.AllowInsecureAuth = true
  1313. go func() {
  1314. require.Nil(t, s.Serve(l))
  1315. }()
  1316. c, err = net.Dial("tcp", l.Addr().String())
  1317. if err != nil {
  1318. t.Fatal(err)
  1319. }
  1320. scanner = bufio.NewScanner(c)
  1321. return
  1322. }
  1323. func writeAndReadUntilLine(t *testing.T, email string, conn net.Conn, scanner *bufio.Scanner, expectedLine string) {
  1324. _, err := io.WriteString(conn, email)
  1325. require.Nil(t, err)
  1326. readUntilLine(t, conn, scanner, expectedLine)
  1327. }
  1328. func readUntilLine(t *testing.T, conn net.Conn, scanner *bufio.Scanner, expectedLine string) {
  1329. cancelChan := make(chan bool)
  1330. go func() {
  1331. select {
  1332. case <-cancelChan:
  1333. case <-time.After(3 * time.Second):
  1334. conn.Close()
  1335. t.Error("Failed waiting for expected output")
  1336. }
  1337. }()
  1338. var output string
  1339. for scanner.Scan() {
  1340. text := scanner.Text()
  1341. if strings.TrimSpace(text) == expectedLine {
  1342. cancelChan <- true
  1343. return
  1344. }
  1345. output += text + "\n"
  1346. }
  1347. t.Fatalf("Expected line '%s' not found in output:\n%s", expectedLine, output)
  1348. }