Move Http utils to jrw.util.
[jrw.git] / src / jrw / FormData.java
CommitLineData
3e20c35c
FT
1package jrw;
2
e00cf9e8 3import jrw.util.*;
3e20c35c
FT
4import java.util.*;
5import java.util.function.*;
6import java.io.*;
7import java.nio.*;
8import java.nio.channels.*;
9
10public 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}