httpTransformer_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /*
  2. * Copyright (c) 2023, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package transforms
  20. import (
  21. "bytes"
  22. "context"
  23. "errors"
  24. "fmt"
  25. "io"
  26. "math"
  27. "net"
  28. "net/http"
  29. "strings"
  30. "testing"
  31. "time"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  33. )
  34. func TestHTTPTransformerHTTPRequest(t *testing.T) {
  35. type test struct {
  36. name string
  37. input string
  38. wantOutput string
  39. wantError error
  40. chunkSize int
  41. transform Spec
  42. connWriteLimit int
  43. connWriteLens []int
  44. connWriteErrs []error
  45. }
  46. tests := []test{
  47. {
  48. name: "written in chunks",
  49. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  50. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  51. chunkSize: 1,
  52. },
  53. {
  54. name: "write header then body", // behaviour of net/http code
  55. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  56. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  57. chunkSize: 38,
  58. },
  59. {
  60. name: "write header then body with error",
  61. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  62. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  63. chunkSize: 38,
  64. connWriteErrs: []error{nil, errors.New("err1")},
  65. wantError: errors.New("err1"),
  66. },
  67. {
  68. name: "written in a single write",
  69. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  70. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  71. chunkSize: 999,
  72. },
  73. {
  74. name: "written in single write with error",
  75. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  76. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  77. chunkSize: 999,
  78. connWriteErrs: []error{errors.New("err1")},
  79. wantError: errors.New("err1"),
  80. },
  81. {
  82. name: "written with partial write and errors",
  83. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  84. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  85. chunkSize: 1,
  86. connWriteLimit: 1,
  87. connWriteErrs: []error{errors.New("err1"), errors.New("err2")},
  88. wantError: errors.New("err1"),
  89. },
  90. {
  91. name: "transform not applied to body",
  92. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  93. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  94. chunkSize: 1,
  95. transform: Spec{[2]string{"abcd", "efgh"}},
  96. },
  97. {
  98. name: "Content-Length missing",
  99. input: "POST / HTTP/1.1\r\n\r\nabcd",
  100. wantError: errors.New("Content-Length missing"),
  101. chunkSize: 1,
  102. },
  103. {
  104. name: "Content-Length overflow",
  105. input: fmt.Sprintf("POST / HTTP/1.1\r\nContent-Length: %d\r\n\r\nabcd", uint64(math.MaxUint64)),
  106. wantError: errors.New("strconv.ParseUint: parsing \"18446744073709551615\": value out of range"),
  107. chunkSize: 1,
  108. },
  109. {
  110. name: "incorrect Content-Length header value",
  111. input: "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabcd",
  112. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc",
  113. chunkSize: 1,
  114. },
  115. {
  116. name: "transform",
  117. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  118. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 100\r\n\r\nabcd",
  119. chunkSize: 1,
  120. transform: Spec{[2]string{"4", "100"}},
  121. },
  122. {
  123. name: "transform with partial write and errors in header write",
  124. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  125. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 100\r\n\r\nabcd",
  126. chunkSize: 1,
  127. transform: Spec{[2]string{"4", "100"}},
  128. connWriteLimit: 1,
  129. connWriteErrs: []error{errors.New("err1"), errors.New("err2")},
  130. wantError: errors.New("err1"),
  131. },
  132. {
  133. name: "transform with chunk write and errors in body write",
  134. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  135. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 100\r\n\r\nabcd",
  136. chunkSize: 39,
  137. transform: Spec{[2]string{"4", "100"}},
  138. connWriteLimit: 1,
  139. connWriteErrs: []error{errors.New("err1"), errors.New("err2"), errors.New("err3")},
  140. wantError: errors.New("err1"),
  141. },
  142. //
  143. // Below tests document unsupported behavior.
  144. //
  145. {
  146. name: "written in a single write with errors and partial writes",
  147. input: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n",
  148. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 0\r\n\r\n",
  149. chunkSize: 999,
  150. transform: Spec{[2]string{"Host: example.com\r\n", ""}},
  151. connWriteErrs: []error{errors.New("err1"), nil, errors.New("err2"), nil, nil, errors.New("err3")},
  152. connWriteLens: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
  153. wantError: errors.New("err1"),
  154. },
  155. {
  156. name: "written in a single write with error and partial write",
  157. input: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  158. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  159. chunkSize: 999,
  160. transform: Spec{[2]string{"Host: example.com\r\n", ""}},
  161. connWriteErrs: []error{errors.New("err1")},
  162. connWriteLens: []int{28}, // write lands mid "\r\n\r\n"
  163. wantError: errors.New("err1"),
  164. },
  165. {
  166. name: "multiple HTTP requests written in a single write",
  167. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 2\r\n\r\n12",
  168. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 2\r\n\r\n12",
  169. chunkSize: 999,
  170. wantError: errors.New("multiple HTTP requests in single Write() not supported"),
  171. },
  172. {
  173. name: "multiple HTTP requests written in a single write with transform",
  174. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 4\r\n\r\n12POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\n34",
  175. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 100\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 100\r\n\r\n12POST / HTTP/1.1\r\nContent-Length: 100\r\n\r\n34",
  176. chunkSize: 999,
  177. transform: Spec{[2]string{"4", "100"}},
  178. wantError: errors.New("multiple HTTP requests in single Write() not supported"),
  179. },
  180. // Multiple HTTP requests written in a single write. A write will occur
  181. // where it contains both the end of the previous HTTP request and the
  182. // start of a new one.
  183. {
  184. name: "multiple HTTP requests written in chunks",
  185. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 2\r\n\r\n12",
  186. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 2\r\n\r\n12",
  187. chunkSize: 4,
  188. wantError: errors.New("multiple HTTP requests in single Write() not supported"),
  189. },
  190. // Multiple HTTP requests written in a single write with transform. A
  191. // write will occur where it contains both the end of the previous HTTP
  192. // request and the start of a new one.
  193. {
  194. name: "multiple HTTP requests written in chunks with transform",
  195. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 4\r\n\r\n12",
  196. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 100\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 100\r\n\r\n12",
  197. chunkSize: 4, // ensure one write contains bytes from both reqs
  198. transform: Spec{[2]string{"4", "100"}},
  199. wantError: errors.New("multiple HTTP requests in single Write() not supported"),
  200. },
  201. }
  202. for _, tt := range tests {
  203. t.Run(tt.name, func(t *testing.T) {
  204. seed, err := prng.NewSeed()
  205. if err != nil {
  206. t.Fatalf("prng.NewSeed failed %v", err)
  207. }
  208. conn := testConn{
  209. writeLimit: tt.connWriteLimit,
  210. writeLens: tt.connWriteLens,
  211. writeErrs: tt.connWriteErrs,
  212. }
  213. transformer := &HTTPTransformer{
  214. transform: tt.transform,
  215. seed: seed,
  216. Conn: &conn,
  217. }
  218. remain := []byte(tt.input)
  219. // Write input bytes to transformer in chunks and then check
  220. // output.
  221. for {
  222. if len(remain) == 0 {
  223. break
  224. }
  225. var b []byte
  226. if len(remain) < tt.chunkSize || tt.chunkSize == 0 {
  227. b = remain
  228. } else {
  229. b = remain[:tt.chunkSize]
  230. }
  231. var n int
  232. n, err = transformer.Write(b)
  233. if err != nil {
  234. // The underlying transport will be a reliable stream
  235. // transport, i.e. TCP, and we expect the caller to stop
  236. // writing after an error is returned.
  237. break
  238. }
  239. remain = remain[n:]
  240. }
  241. if tt.wantError == nil {
  242. if err != nil {
  243. t.Fatalf("unexpected error %v", err)
  244. }
  245. if string(conn.b) != tt.wantOutput {
  246. t.Fatalf("expected \"%s\" of len %d but got \"%s\" of len %d", escapeNewlines(tt.wantOutput), len(tt.wantOutput), escapeNewlines(string(conn.b)), len(conn.b))
  247. }
  248. } else {
  249. // tt.wantError != nil
  250. if err == nil {
  251. t.Fatalf("expected error %v", tt.wantError)
  252. } else if !strings.Contains(err.Error(), tt.wantError.Error()) {
  253. t.Fatalf("expected error %v got %v", tt.wantError, err)
  254. }
  255. }
  256. })
  257. }
  258. }
  259. func TestHTTPTransformerHTTPServer(t *testing.T) {
  260. type test struct {
  261. name string
  262. request func(string) *http.Request
  263. wantBody string
  264. transform Spec
  265. connWriteLimit int
  266. connWriteLens []int
  267. connWriteErrs []error
  268. wantError error
  269. }
  270. tests := []test{
  271. {
  272. name: "request body truncated",
  273. transform: Spec{[2]string{"Content-Length: 4", "Content-Length: 3"}},
  274. request: func(addr string) *http.Request {
  275. body := bytes.NewReader([]byte("abcd"))
  276. req, err := http.NewRequest("POST", "http://"+addr, body)
  277. if err != nil {
  278. panic(err)
  279. }
  280. return req
  281. },
  282. wantBody: "abc",
  283. },
  284. // Expect HTTP request to abort after a single Write() call on
  285. // underlying net.Conn fails.
  286. {
  287. name: "transport fails",
  288. transform: Spec{[2]string{"", ""}},
  289. request: func(addr string) *http.Request {
  290. body := bytes.NewReader([]byte("abcd"))
  291. req, err := http.NewRequest("POST", "http://"+addr, body)
  292. if err != nil {
  293. panic(err)
  294. }
  295. return req
  296. },
  297. wantBody: "abc",
  298. connWriteErrs: []error{errors.New("test error")},
  299. wantError: errors.New("test error"),
  300. },
  301. }
  302. for _, tt := range tests {
  303. t.Run(tt.name, func(t *testing.T) {
  304. seed, err := prng.NewSeed()
  305. if err != nil {
  306. t.Fatalf("prng.NewSeed failed %v", err)
  307. }
  308. params := &HTTPTransformerParameters{
  309. ProtocolTransformName: "spec",
  310. ProtocolTransformSpec: tt.transform,
  311. ProtocolTransformSeed: seed,
  312. }
  313. dialer := func(ctx context.Context, network, address string) (net.Conn, error) {
  314. if network != "tcp" {
  315. return nil, errors.New("expected network tcp")
  316. }
  317. conn, err := net.Dial(network, address)
  318. if err != nil {
  319. return nil, err
  320. }
  321. wrappedConn := testConn{
  322. Conn: conn,
  323. writeLimit: tt.connWriteLimit,
  324. writeLens: tt.connWriteLens,
  325. writeErrs: tt.connWriteErrs,
  326. }
  327. return &wrappedConn, nil
  328. }
  329. httpTransport := &http.Transport{
  330. DialContext: WrapDialerWithHTTPTransformer(dialer, params),
  331. }
  332. type serverRequest struct {
  333. req *http.Request
  334. body []byte
  335. }
  336. serverReq := make(chan *serverRequest, 1)
  337. mux := http.NewServeMux()
  338. mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  339. b, err := io.ReadAll(r.Body)
  340. if err != nil {
  341. w.WriteHeader(http.StatusInternalServerError)
  342. }
  343. go func() {
  344. serverReq <- &serverRequest{
  345. req: r,
  346. body: b,
  347. }
  348. close(serverReq)
  349. }()
  350. })
  351. listener, err := net.Listen("tcp", "127.0.0.1:0")
  352. if err != nil {
  353. t.Fatalf("net.Listen failed %v", err)
  354. }
  355. s := &http.Server{
  356. Addr: listener.Addr().String(),
  357. Handler: mux,
  358. }
  359. go func() {
  360. s.Serve(listener)
  361. }()
  362. client := http.Client{
  363. Transport: httpTransport,
  364. Timeout: 2 * time.Second,
  365. }
  366. req := tt.request(s.Addr)
  367. resp, err := client.Do(req)
  368. // first shutdown server, then check err
  369. shutdownErr := s.Shutdown(context.Background())
  370. if shutdownErr != nil {
  371. t.Fatalf("s.Shutdown failed %v", shutdownErr)
  372. }
  373. if tt.wantError == nil {
  374. if err != nil {
  375. t.Fatalf("client.Do failed %v", err)
  376. }
  377. if resp.StatusCode != http.StatusOK {
  378. t.Fatalf("expected 200 but got %d", resp.StatusCode)
  379. }
  380. r := <-serverReq
  381. if tt.wantBody != string(r.body) {
  382. t.Fatalf("expected body %s but got %s", tt.wantBody, string(r.body))
  383. }
  384. } else {
  385. // tt.wantError != nil
  386. if err == nil {
  387. t.Fatalf("expected error %v", tt.wantError)
  388. } else if !strings.Contains(err.Error(), tt.wantError.Error()) {
  389. t.Fatalf("expected error %v got %v", tt.wantError, err)
  390. }
  391. }
  392. })
  393. }
  394. }
  395. func escapeNewlines(s string) string {
  396. s = strings.ReplaceAll(s, "\n", "\\n")
  397. s = strings.ReplaceAll(s, "\r", "\\r")
  398. return s
  399. }
  400. type testConn struct {
  401. // b is the accumulated bytes from Write() calls.
  402. b []byte
  403. // writeLimit is the max number of bytes that will be written in a Write()
  404. // call.
  405. writeLimit int
  406. // writeLens are returned from Write() calls in order and determine the
  407. // max number of bytes that will be written. Overrides writeLimit if
  408. // non-empty. If empty, then the value of writeLimit is returned.
  409. writeLens []int
  410. // writeErrs are returned from Write() calls in order. If empty, then a nil
  411. // error is returned.
  412. writeErrs []error
  413. net.Conn
  414. }
  415. func (c *testConn) Read(b []byte) (n int, err error) {
  416. return c.Conn.Read(b)
  417. }
  418. func (c *testConn) Write(b []byte) (n int, err error) {
  419. if len(c.writeErrs) > 0 {
  420. err = c.writeErrs[0]
  421. c.writeErrs = c.writeErrs[1:]
  422. }
  423. if len(c.writeLens) > 0 {
  424. n = c.writeLens[0]
  425. c.writeLens = c.writeLens[1:]
  426. if len(b) <= n {
  427. c.b = append(c.b, b...)
  428. n = len(b)
  429. } else {
  430. c.b = append(c.b, b[:n]...)
  431. }
  432. } else if c.writeLimit != 0 && c.writeLimit < len(b) {
  433. c.b = append(c.b, b[:c.writeLimit]...)
  434. n = c.writeLimit
  435. } else {
  436. c.b = append(c.b, b...)
  437. n = len(b)
  438. }
  439. // Only write to net.Conn if set
  440. if c.Conn != nil && n > 0 {
  441. c.Conn.Write(b[:n])
  442. }
  443. return
  444. }
  445. func (c *testConn) Close() error {
  446. return c.Conn.Close()
  447. }
  448. func (c *testConn) LocalAddr() net.Addr {
  449. return c.Conn.LocalAddr()
  450. }
  451. func (c *testConn) RemoteAddr() net.Addr {
  452. return c.Conn.RemoteAddr()
  453. }
  454. func (c *testConn) SetDeadline(t time.Time) error {
  455. return c.Conn.SetDeadline(t)
  456. }
  457. func (c *testConn) SetReadDeadline(t time.Time) error {
  458. return c.Conn.SetReadDeadline(t)
  459. }
  460. func (c *testConn) SetWriteDeadline(t time.Time) error {
  461. return c.Conn.SetWriteDeadline(t)
  462. }