Commit | Line | Data |
---|---|---|
3e20c35c FT |
1 | package jrw; |
2 | ||
e00cf9e8 | 3 | import jrw.util.*; |
3e20c35c FT |
4 | import java.util.*; |
5 | import java.util.function.*; | |
6 | import java.io.*; | |
7 | import java.nio.*; | |
8 | import java.nio.channels.*; | |
9 | ||
10 | public class FormData extends HashMap<String, String> { | |
11 | public static final int MAX_LENGTH = 1 << 20; | |
12 | ||
13 | private static int htoi(byte hex) { | |
14 | if((hex >= '0') && (hex <= '9')) | |
15 | return(hex - '0'); | |
16 | if((hex >= 'A') && (hex <= 'F')) | |
17 | return(hex - 'A' + 10); | |
18 | if((hex >= 'a') && (hex <= 'f')) | |
19 | return(hex - 'a' + 10); | |
20 | return(0); | |
21 | } | |
22 | ||
23 | private static ByteBuffer unquoteb(ByteBuffer part) { | |
24 | ByteBuffer ret = ByteBuffer.allocate(part.remaining()); | |
25 | while(part.remaining() > 0) { | |
26 | int b = part.get() & 0xff; | |
27 | if((b == '%') && (part.remaining() >= 2)) { | |
28 | int n1 = htoi(part.get()), n2 = htoi(part.get()); | |
29 | ret.put((byte)((n1 << 4) | n2)); | |
30 | } else { | |
31 | ret.put((byte)b); | |
32 | } | |
33 | } | |
34 | ret.flip(); | |
35 | return(ret); | |
36 | } | |
37 | ||
38 | private static String unquote(ByteBuffer part) { | |
39 | ByteBuffer dec = unquoteb(part); | |
40 | try { | |
41 | return(Http.UTF8.newDecoder().decode(dec.duplicate()).toString()); | |
42 | } catch(java.nio.charset.CharacterCodingException e) { | |
43 | return(Http.LATIN1.decode(dec).toString()); | |
44 | } | |
45 | } | |
46 | ||
47 | private static String unquote(String part) { | |
48 | if(part.indexOf('%') < 0) | |
49 | return(part); | |
50 | return(unquote(Http.UTF8.encode(CharBuffer.wrap(part)))); | |
51 | } | |
52 | ||
53 | public static void parse(Map<? super String, ? super String> buf, ByteBuffer data) { | |
54 | int p = data.position(), p2, p3; | |
55 | while(p < data.limit()) { | |
56 | for(p2 = p; (p2 < data.limit()) && (data.get(p2) != '&'); p2++); | |
57 | for(p3 = p; (p3 < p2) && (data.get(p3) != '='); p3++); | |
58 | if(p3 < p2) { | |
59 | buf.put(unquote((ByteBuffer)data.duplicate().position(p).limit(p3)), | |
60 | unquote((ByteBuffer)data.duplicate().position(p3 + 1).limit(p2))); | |
61 | } | |
62 | p = p2 + 1; | |
63 | } | |
64 | } | |
65 | ||
66 | public static void parse(Map<? super String, ? super String> buf, String data) { | |
67 | int p = 0; | |
68 | while(true) { | |
69 | int p2 = data.indexOf('&', p); | |
70 | String part = (p2 < 0) ? data.substring(p) : data.substring(p, p2); | |
71 | int p3 = part.indexOf('='); | |
72 | if(p3 >= 0) | |
73 | buf.put(unquote(part.substring(0, p3)), unquote(part.substring(p3 + 1))); | |
74 | if(p2 < 0) | |
75 | break; | |
76 | p = p2 + 1; | |
77 | } | |
78 | } | |
79 | ||
80 | public static FormData read(Request req) { | |
81 | FormData ret = new FormData(); | |
82 | String query = (String)req.env.get("QUERY_STRING"); | |
83 | if(query != null) | |
84 | parse(ret, query); | |
85 | if(req.ihead("Content-Type", "").equals("application/x-www-form-urlencoded")) { | |
86 | int max = MAX_LENGTH; | |
87 | String clen = req.ihead("Content-Length", null); | |
88 | if(clen != null) { | |
89 | try { | |
90 | max = Math.min(max, Integer.parseInt(clen)); | |
91 | } catch(NumberFormatException e) { | |
92 | } | |
93 | } | |
94 | ReadableByteChannel in = (ReadableByteChannel)req.env.get("jagi.input"); | |
95 | if(in instanceof SelectableChannel) { | |
96 | try { | |
97 | ((SelectableChannel)in).configureBlocking(true); | |
98 | } catch(IOException e) { | |
99 | } | |
100 | } | |
101 | ByteBuffer buf = ByteBuffer.allocate(65536); | |
102 | while(buf.position() < max) { | |
103 | if(buf.remaining() == 0) { | |
104 | ByteBuffer n = ByteBuffer.allocate(Math.min(buf.capacity() * 2, max)); | |
105 | buf.flip(); | |
106 | n.put(buf); | |
107 | buf = n; | |
108 | } | |
109 | try { | |
110 | int rv = in.read(buf); | |
111 | if(rv <= 0) | |
112 | break; | |
113 | } catch(IOException e) { | |
114 | break; | |
115 | } | |
116 | } | |
117 | buf.flip(); | |
118 | parse(ret, buf); | |
119 | } | |
120 | return(ret); | |
121 | } | |
122 | ||
123 | public static FormData get(Request req) { | |
124 | FormData ret = (FormData)req.env.get(FormData.class); | |
125 | if(ret == null) | |
126 | req.env.put(FormData.class, ret = read(req)); | |
127 | return(ret); | |
128 | } | |
129 | ||
130 | static class Collector extends ByteArrayOutputStream { | |
131 | final FormData form = new FormData(); | |
132 | final Request req; | |
133 | ||
134 | Collector(Request req) { | |
135 | this.req = req; | |
136 | req.env.put(FormData.class, this.form); | |
137 | String query = (String)req.env.get("QUERY_STRING"); | |
138 | if(query != null) | |
139 | parse(form, query); | |
140 | } | |
141 | ||
142 | public void write(int b) { | |
143 | if(count < MAX_LENGTH) | |
144 | super.write(b); | |
145 | } | |
146 | ||
147 | public void write(byte[] buf, int off, int len) { | |
148 | len = Math.min(len, MAX_LENGTH - count); | |
149 | if(len > 0) | |
150 | super.write(buf, off, len); | |
151 | } | |
152 | ||
153 | public void close() { | |
29793a0f | 154 | parse(form, ByteBuffer.wrap(toByteArray())); |
3e20c35c FT |
155 | } |
156 | } | |
157 | ||
158 | public static Map<Object, Object> feed(Request req, Handler next) { | |
159 | Map<Object, Object> resp = new HashMap<>(); | |
160 | if(req.ihead("Content-Type", "").equals("application/x-www-form-urlencoded")) { | |
161 | resp.put("jagi.status", "feed-input"); | |
d3d92d6c | 162 | resp.put("jagi.next", (Function<Map<Object, Object>, Map<Object, Object>>)env -> Dispatch.handle(next, req)); |
3e20c35c FT |
163 | resp.put("jagi.input-sink", new Collector(req)); |
164 | } else { | |
165 | read(req); | |
166 | resp.put("jagi.status", "chain"); | |
d3d92d6c | 167 | resp.put("jagi.next", (Function<Map<Object, Object>, Map<Object, Object>>)env -> Dispatch.handle(next, req)); |
3e20c35c FT |
168 | } |
169 | return(resp); | |
170 | } | |
171 | } |