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