1 //
2 // Copyright (c) 2007, Brian Frank and Andy Frank
3 // Licensed under the Academic Free License version 3.0
4 //
5 // History:
6 // 27 Jun 07 Brian Frank Creation
7 //
8
9 **
10 ** WebUtil encapsulates several useful utility web methods.
11 **
12 class WebUtil
13 {
14
15 //////////////////////////////////////////////////////////////////////////
16 // Parsing
17 //////////////////////////////////////////////////////////////////////////
18
19 **
20 ** Parse a list of comma separated tokens. Any leading
21 ** or trailing whitespace is trimmed from the list of tokens.
22 **
23 static Str[] parseList(Str s)
24 {
25 // TODO regex when available
26 toks := s.split(",")
27 toks.each |Str tok, Int i| { toks[i] = tok.trim }
28 return toks
29 }
30
31 **
32 ** Parse a series of HTTP headers according to RFC 2616 section
33 ** 4.2. The final CRLF which terminates headers is consumed with
34 ** the stream positioned immediately following. The headers are
35 ** returned as a [case insensitive]`sys::Map.caseInsensitive` map.
36 ** Throw IOErr if headers are malformed.
37 **
static Str:Str parseHeaders(InStream in)
39 {
40 headers := Str:Str[:]
41 headers.caseInsensitive = true
42 Str last := null
43
44 // read headers into map
45 while (true)
46 {
47 peek := in.peek
48
49 // CRLF is end of headers
50 if (peek === CR) break
51
52 // if line starts with space it is
53 // continuation of last header field
54 if (peek.isSpace && last != null)
55 {
56 headers[last] += " " + in.readLine.trim
57 continue
58 }
59
60 // key/value pair
61 key := token(in, ':').trim
62 val := token(in, CR).trim
63 if (in.read != LF)
64 throw IOErr.make("Invalid CRLF line ending")
65
66 // check if key already defined in which case
67 // this is an append, otherwise its a new pair
68 dup := headers[key]
69 if (dup == null)
70 headers[key] = val
71 else
72 headers[key] = dup + "," + val
73 last = key
74 }
75
76 // consume final CRLF
77 if (in.read !== CR || in.read !== LF)
78 throw IOErr.make("Invalid CRLF headers ending")
79
80 return headers
81 }
82
83 **
84 ** Read the next token from the stream up to the specified
85 ** separator. We place a limit of 512 bytes on a single token.
86 ** Consume the separate char too.
87 **
88 private static Str token(InStream in, Int sep)
89 {
90 // read up to separator
91 tok := in.readStrToken(maxTokenSize) |Int ch->Bool| { return ch == sep }
92
93 // sanity checking
94 if (tok == null) throw IOErr.make("Unexpected end of stream")
95 if (tok.size >= 512) throw IOErr.make("Token too big")
96
97 // read separator
98 in.read
99
100 return tok
101 }
102
103 //////////////////////////////////////////////////////////////////////////
104 // Fields
105 //////////////////////////////////////////////////////////////////////////
106
107 internal const static Int CR := '\r'
108 internal const static Int LF := '\n'
109 internal const static Int HT := '\t'
110 internal const static Int SP := ' '
111 internal const static Int maxTokenSize := 512
112
113 }