Fantom

 

//
// Copyright (c) 2007, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   27 Jul 07  Brian Frank  Creation
//

**
** UtilTest
**
@Js
class UtilTest : Test
{

  Void testIsToken()
  {
    verifyEq(WebUtil.isToken(""), false)
    verifyEq(WebUtil.isToken("x"), true)
    verifyEq(WebUtil.isToken("x y"), false)
    verifyEq(WebUtil.isToken("5a-3dd_33*&^%22!~"), true)
    verifyEq(WebUtil.isToken("(foo)"), false)
    verifyEq(WebUtil.isToken("foo;bar"), false)

    // test https://tools.ietf.org/html/rfc7230#section-3.2.6
    chars := Int:Bool[:] { def = false }
    ('0'..'9').each |c| { chars[c] = true }
    ('a'..'z').each |c| { chars[c] = true }
    ('A'..'Z').each |c| { chars[c] = true }
    "!#\$%&'*+-.^_`|~".each |c| { chars[c] = true }
    for (c:=0; c<130; ++c)
      verifyEq(WebUtil.isTokenChar(c), chars[c])
  }

  Void testToQuotedStr()
  {
    verifyQuotedStr("", "\"\"")
    verifyQuotedStr("foo bar", "\"foo bar\"")
    verifyQuotedStr("foo\"bar\"baz", "\"foo\\\"bar\\\"baz\"")

    verifyErr(ArgErr#) { WebUtil.toQuotedStr("foo\nbar") }
    verifyErr(ArgErr#) { WebUtil.toQuotedStr("\u007f") }
    verifyErr(ArgErr#) { WebUtil.toQuotedStr("\u024a") }

    verifyErr(ArgErr#) { WebUtil.fromQuotedStr("") }
    verifyErr(ArgErr#) { WebUtil.fromQuotedStr("\"") }
    verifyErr(ArgErr#) { WebUtil.fromQuotedStr("\"x") }
    verifyErr(ArgErr#) { WebUtil.fromQuotedStr("x\"") }
  }

  Void verifyQuotedStr(Str s, Str expected)
  {
    verifyEq(WebUtil.toQuotedStr(s), expected)
    verifyEq(WebUtil.fromQuotedStr(expected), s)
  }

  Void testParseList()
  {
    verifyEq(WebUtil.parseList("a"), ["a"])
    verifyEq(WebUtil.parseList(" a "), ["a"])
    verifyEq(WebUtil.parseList("a, bob, c,delta "), ["a", "bob", "c", "delta"])
  }

  Void testParseHeaders()
  {
    in :=
      ("Host: foobar\r\n" +
      "Extra1:  space\r\n" +
      "Extra2: space  \r\n" +
      "Cont: one two \r\n" +
      "  three\r\n" +
      "\tfour\r\n" +
      "Coalesce: a,b\r\n" +
      "Coalesce: c\r\n" +
      "Coalesce:  d\r\n" +
      "\r\n").toBuf.in

     headers := WebUtil.parseHeaders(in)
     verifyEq(headers.caseInsensitive, true)
     verifyEq(headers,
       [
        "Host":     "foobar",
        "Extra1":   "space",
        "Extra2":   "space",
        "Cont":     "one two three four",
        "Coalesce": "a,b,c,d",
       ])
  }

  Void testChunkInStream()
  {
    str := "3\r\nxyz\r\nB\r\nhello there\r\n0\r\n\r\n"

    // readAllStr
    in := WebUtil.makeChunkedInStream(str.toBuf.in)
    verifyEq(in.readAllStr, "xyzhello there")

    // readBuf chunks
    in = WebUtil.makeChunkedInStream(str.toBuf.in)
    buf := Buf()
    verifyEq(in.readBuf(buf.clear, 20), 3)
    verifyEq(buf.flip.readAllStr, "xyz")
    verifyEq(in.readBuf(buf.clear, 20), 11)
    verifyEq(buf.flip.readAllStr, "hello there")
    verifyEq(in.readBuf(buf.clear, 20), null)
    verifyEq(in.readBuf(buf.clear, 20), null)

    // readBufFully
    in = WebUtil.makeChunkedInStream(str.toBuf.in)
    in.readBufFully(buf.clear, 14)
    verifyEq(buf.readAllStr, "xyzhello there")
    verifyEq(in.read, null)
    verifyEq(in.readChar, null)
    verifyEq(in.read, null)

    // unread
    in = WebUtil.makeChunkedInStream(str.toBuf.in)
    verifyEq(in.read, 'x')
    verifyEq(in.read, 'y')
    in.unread('?')
    verifyEq(in.read, '?')
    in.unread('2').unread('1')
    in.readBufFully(buf.clear, 14)
    verifyEq(buf.readAllStr, "12zhello there")

    // fixed chunked stream
    in = WebUtil.makeFixedInStream("abcdefgh".toBuf.in, 3)
    verifyEq(in.readAllStr, "abc")
  }

  Void testFixedOutStream()
  {
    buf := Buf()
    out := WebUtil.makeFixedOutStream(buf.out, 4)
    out.print("abcd")
    verifyErr(IOErr#) { out.write('x') }
    verifyEq(buf.flip.readAllStr, "abcd")

    buf2 := Buf()
    buf.seek(0)
    out = WebUtil.makeFixedOutStream(buf2.out, 2)
    out.writeBuf(buf, 2)
    verifyErr(IOErr#) { out.writeBuf(buf, 1) }
    verifyEq(buf2.flip.readAllStr, "ab")
  }

  Void testChunkOutStream()
  {
    buf := Buf()
    out := WebUtil.makeChunkedOutStream(buf.out)
    2000.times |Int i| { out.printLine(i) }
    out.close

    in := WebUtil.makeChunkedInStream(buf.flip.in)
    2000.times |Int i| { verifyEq(in.readLine, i.toStr) }
    verifyEq(in.read, null)
  }

  Void testParseQVals()
  {
    verifyEq(WebUtil.parseQVals(""), Str:Float[:])
    verifyEq(WebUtil.parseQVals("compress"), Str:Float["compress":1.0f])
    verifyEq(WebUtil.parseQVals("compress,gzip"), Str:Float["compress":1.0f, "gzip": 1.0f])
    verifyEq(WebUtil.parseQVals("compress;q=0.7,gzip;q=0.0"), Str:Float["compress":0.7f, "gzip": 0f])

    q := WebUtil.parseQVals("foo , compress ; q=0.8 , gzip ; q=0.5 , bar; q=x")
    verifyEq(q, Str:Float["foo":1.0f, "compress": 0.8f, "gzip": 0.5f, "bar":1.0f])
    verifyEq(q["compress"], 0.8f)
    verifyEq(q["def"], 0.0f)
  }

  Void testParseMultiPart()
  {
    // couple empty posts
    s :=
    """------WebKitFormBoundaryvx0NalAyBZjdpZAe
       Content-Disposition: form-data; name="file1"; filename="empty.txt"


       ------WebKitFormBoundaryvx0NalAyBZjdpZAe
       Content-Disposition: form-data; name="file2"; filename="empty.txt"


       ------WebKitFormBoundaryvx0NalAyBZjdpZAe--
       """

    boundary := "----WebKitFormBoundaryvx0NalAyBZjdpZAe"
    WebUtil.parseMultiPart(s.replace("\n", "\r\n").toBuf.in, boundary) |h, in|
    {
      verify(h["Content-Disposition"].startsWith("form-data"))
      verifyEq(in.readAllStr, "")
    }

    // test real post from IE using test data below
    boundary = "---------------------------7dacb195e0632"
    base64 :=
      "LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS03ZGFjYjE5NWUwNjMyDQpDb250ZW5
       0LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImZpbGUxIjsgZmlsZW5hbWU9Ik
       M6XGRldlxmYW5cbXVsdGlwYXJ0LWEudHh0Ig0KQ29udGVudC1UeXBlOiB0ZXh0L3BsY
       WluDQoNCmZvbyBiYXINCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tN2RhY2Ix
       OTVlMDYzMg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWx
       lMiI7IGZpbGVuYW1lPSJDOlxkZXZcZmFuXG11bHRpcGFydC1iLnR4dCINCkNvbnRlbn
       QtVHlwZTogdGV4dC9wbGFpbg0KDQoAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobH
       B0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PT4/QEFCQ0RFRkdISUpLTE1O
       T1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4C
       BgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7Cxsr
       O0tba3uLm6u7y9vr/AwcLDxMXGxw0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tL
       S03ZGFjYjE5NWUwNjMyDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5h
       bWU9ImZpbGUzIjsgZmlsZW5hbWU9IkM6XGRldlxmYW5cbXVsdGlwYXJ0LWMudHh0Ig0
       KQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluDQoNCi0tLS0tLS0NCi0tLS0tLS0NCi0tLS
       0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tN2RhY2IxOTVlMDYzMi0tDQo="
    count := 0
    WebUtil.parseMultiPart(Buf.fromBase64(base64).in, boundary) |h, in|
    {
      switch (count++)
      {
        // verify no extra newlines
        case 0:
          verifyEq(in.readAllStr, "foo bar")

        // verify binary data, readBuf, and unread
        case 1:
          100.times |i| { verifyEq(in.readU1, i) }
          in.unread(0xab).unread(0xcd)
          verifyEq(in.readU2, 0xcdab)
          buf := Buf()
          in.readBufFully(buf, 100)
          100.times |i| { verifyEq(buf[i], 100+i) }
          verifyNull(in.read)

        // verify data that might look like boundaries
        case 2:
          verifyEq(in.readAllStr(false), "-------\r\n-------")
      }
    }

    // single item
    boundary = "---------------------------41184676334"
    s =
     """-----------------------------41184676334
        Content-Disposition: form-data; name=""; filename="something.txt"
        Content-Type: text/plain

        hello world
        -----------------------------41184676334--
        """
    buf := Buf()
    numRead := 0
    WebUtil.parseMultiPart(s.replace("\n", "\r\n").toBuf.in, boundary) |h, in|
    {
      in.pipe(buf.out)
      numRead = in->numRead
    }
    verifyEq(buf.flip.readAllStr, "hello world")
    verifyEq(numRead, 11)

    // mixed
    s =
     """------WebKitFormBoundaryZ7PdCaCUA42JfPxv
        Content-Disposition: form-data; name="name"

        FooBar
        ------WebKitFormBoundaryZ7PdCaCUA42JfPxv
        Content-Disposition: form-data; name="ver"

        1.0.5
        ------WebKitFormBoundaryZ7PdCaCUA42JfPxv
        Content-Disposition: form-data; name="file"; filename="temp.txt"
        Content-Type: application/octet-stream

        Hello,
        World
        :)
        ------WebKitFormBoundaryZ7PdCaCUA42JfPxv--
        """
    boundary = "----WebKitFormBoundaryZ7PdCaCUA42JfPxv"
    numRead = 0
    WebUtil.parseMultiPart(s.replace("\n", "\r\n").toBuf.in, boundary) |h, in|
    {
      numRead++
      disp := h["Content-Disposition"]
      verify(disp.startsWith("form-data;"))

      map := MimeType.parseParams(disp["form-data;".size..-1].trim)
      switch (map["name"])
      {
        case "name": verifyEq(in.readAllStr, "FooBar")
        case "ver":  verifyEq(in.readAllStr, "1.0.5")
        case "file": verifyEq(in.readAllStr, "Hello,\nWorld\n:)")
      }
    }
    verifyEq(numRead, 3)
  }

  // generate test files for testParseMultiPart
  static Void main(Str[] args)
  {
    `multipart-a`.toFile.out.print("foo bar").close

    out := `multipart-b`.toFile.out
    200.times |i| { out.write(i) }
    out.close

    `multipart-c`.toFile.out.print("-------\r\n-------").close
  }

}