1 module hunt.http.codec.CommonDecoder;
2 
3 import hunt.io.ByteBuffer;
4 import hunt.io.BufferUtils;
5 import hunt.io.channel;
6 import hunt.Exceptions;
7 import hunt.logging;
8 
9 import hunt.http.HttpConnection;
10 import hunt.http.HttpConnection;
11 import hunt.http.HttpConnection;
12 import hunt.net.codec.Decoder;
13 import hunt.net.Connection;
14 import hunt.net.Exceptions;
15 import hunt.net.secure.SecureSession;
16 import hunt.util.DateTime;
17 
18 /**
19  * 
20  */
21 class CommonDecoder : DecoderChain {
22 
23     this(DecoderChain next) {
24         super(next);
25     }
26 
27     override DataHandleStatus decode(ByteBuffer buf, Connection session) {
28         DataHandleStatus resultStatus = DataHandleStatus.Done;
29 
30         version(HUNT_METRIC) {
31             import core.time;
32             MonoTime startTime = MonoTime.currTime;
33             debug infof("start decoding ...");
34         }
35 
36         version(WITH_HUNT_SECURITY) {
37             try {
38                 resultStatus = decodeSecureSession(buf, session);
39             } catch(SSLHandshakeException ex) {
40                 warning(ex.msg);
41                 session.write(ErrorResponseMessage);
42                 // session.close();
43             } catch(Exception ex) {
44                 warning(ex.msg);
45                 version(HUNT_HTTP_DEBUG) warning(ex);
46                 version(HUNT_DEBUG) {
47                     session.write(ex.msg);
48                 } else {
49                     session.write(ex.msg);
50                 }
51                 session.close();
52             }
53         } else {
54            resultStatus = decodePlaintextSession(buf, session);
55         }
56 
57         version(HUNT_METRIC) {
58             Duration timeElapsed = MonoTime.currTime - startTime;
59             warningf("decoding done for session %d in: %d microseconds",
60                 session.getId, timeElapsed.total!(TimeUnit.Microsecond)());
61         }
62 
63         return resultStatus;
64     }
65 
66     private DataHandleStatus decodePlaintextSession(ByteBuffer buf, Connection session) {
67         DataHandleStatus resultStatus = DataHandleStatus.Done;
68 
69         ConnectionState connState;
70         do {
71             connState = session.getState();
72             version(HUNT_HTTP_DEBUG) {
73                 if(connState == ConnectionState.Opening)
74                     warning("Waiting for a http session...");
75             }
76         } while(connState == ConnectionState.Opening);
77         
78         version(HUNT_HTTP_DEBUG) {
79             infof("ConnectionState: %s", connState);
80         }
81             
82         DecoderChain next = getNext();
83         if (next !is null) {
84             resultStatus = next.decode(buf, session);
85         } else {
86             warning("The next decoder is null.");
87             BufferUtils.clear(buf);
88         }
89 
90         return resultStatus;
91     }
92 
93     private DataHandleStatus decodeSecureSession(ByteBuffer buf, Connection session) {
94         ConnectionState connState = session.getState();
95         DataHandleStatus resultStatus = DataHandleStatus.Done;
96             
97         version(HUNT_HTTP_DEBUG) {
98             infof("ConnectionState: %s", connState);
99         }
100 
101         if(connState == ConnectionState.Secured) {
102             DecoderChain next = getNext();
103             SecureSession secureSession = cast(SecureSession) session.getAttribute(SecureSession.NAME);     
104             assert(secureSession !is null, "secureSession is null");
105 
106             // version(HUNT_HTTP_DEBUG) tracef("Raw buffer: %s", buf.toString());
107             ByteBuffer plaintext = secureSession.read(buf); // httpConnection.decrypt(buf);
108 
109             if (plaintext !is null && plaintext.hasRemaining() && next !is null) {
110                 version(HUNT_HTTP_DEBUG) {
111                     infof("decrypted buffer: %s", plaintext.toString());
112                 }
113                 version(HUNT_HTTP_DEBUG_MORE) {
114                     // int r = plaintext.remaining();
115                     // if(r < 64) {
116                     //     tracef("%(%02X %)", plaintext.peekRemaining());
117                     // }
118                     string msg = cast(string)plaintext.peekRemaining();
119                     trace(msg);
120                     // tracef("%(%02X %)", plaintext.peekRemaining());
121                 }
122        
123                 resultStatus = next.decode(plaintext, session);
124             } else {
125                 version(HUNT_HTTP_DEBUG) warning("No data decrypted!");
126             }
127         } else if(connState == ConnectionState.Securing) {
128             version(HUNT_DEBUG) {
129                 info("TLS handshaking...");
130             }
131             // TLS handshake
132             enum int MaxTimes = 5;
133             SecureSession secureSession = waitForSecureSession(MaxTimes, session);
134 
135             if(secureSession is null) {
136                 version(HUNT_DEBUG) warning("Running handshake in another thread.");
137                 import std.parallelism;
138                 // auto handshakeTask = task(&handleTlsHandshake, buf, session, secureSession, next);
139                 auto handshakeTask = task(() {
140                     // 
141                     // FIXME: Needing refactor or cleanup -@zxp at 8/8/2019, 4:29:49 PM
142                     // Maybe the buf needs be copied.
143                     SecureSession s = waitForSecureSession(0, session);
144                     if(s is null) {
145                         warning("No SecureSession created");
146                     } else {
147                         handleTlsHandshake(buf, session, s);
148                     }
149                 });
150                 taskPool.put(handshakeTask);
151             } else {
152                 handleTlsHandshake(buf, session, secureSession);
153             }
154 
155         } else {
156            resultStatus = decodePlaintextSession(buf, session);
157         }
158 
159         return resultStatus;
160     }
161 
162     private SecureSession waitForSecureSession(int maxTimes, Connection session) {
163         SecureSession secureSession;
164 
165         int count = 0;
166         if(maxTimes>0) {
167             do {
168                 secureSession = cast(SecureSession) session.getAttribute(SecureSession.NAME);
169                 count++;
170                 version(HUNT_HTTP_DEBUG) {
171                     if(secureSession is null)
172                         tracef("Waiting for a secure session...%d", count);
173                 }
174             } while(count < maxTimes && secureSession is null); 
175         } else {
176             // Waiting until the SecureSession is avaliable.
177             do {
178                 version(HUNT_HTTP_DEBUG_MORE) {
179                     if(secureSession is null)
180                         trace("Waiting for a secure session...");
181                 }
182                 secureSession = cast(SecureSession) session.getAttribute(SecureSession.NAME);
183             } while(secureSession is null && session.getState() != ConnectionState.Error); 
184         }
185 
186         return secureSession;
187     }
188 
189     private void handleTlsHandshake(ByteBuffer buf, Connection session, 
190         SecureSession secureSession) {
191 
192         DecoderChain next = getNext();
193         ByteBuffer plaintext = secureSession.read(buf);
194 
195         if (plaintext !is null && plaintext.hasRemaining()) {
196             version(HUNT_DEBUG) {
197                 tracef("The session %s handshake finished and received cleartext size %s",
198                         session.getId(), plaintext.remaining());
199             }
200 
201             AbstractHttpConnection httpConnection = cast(AbstractHttpConnection) session.getAttribute(HttpConnection.NAME);
202             version(HUNT_HTTP_DEBUG) {
203                 tracef("http connection: %s", httpConnection is null ? "null" : typeid(httpConnection).name);
204             }
205 
206             if (httpConnection !is null) {
207                 if (next !is null) 
208                     next.decode(plaintext, session);
209                 else 
210                     warning("The next decoder is null.");
211             } else {
212                 warningf("httpConnection is null");
213                 throw new IllegalStateException("the http connection has not been created");
214             }
215         } else {
216             version(HUNT_DEBUG) {
217                 if (secureSession.isHandshakeFinished()) {
218                     tracef("The ssl session %s need more data", session.getId());
219                 } else {
220                     tracef("The ssl session %s is shaking hand", session.getId());
221                 }
222             }
223         }
224     }
225 }
226 
227 
228 // TODO: Tasks pending completion -@zhangxueping at 2019-12-23T19:11:48+08:00
229 // 
230 enum ErrorResponseMessage = "HTTP/1.1 400 Bad Request\n" ~
231 "Server: nginx/1.17.6\n" ~
232 "Date: Mon, 23 Dec 2019 10:54:33 GMT\n" ~
233 "Content-Type: text/html\n" ~
234 "Connection: close\n" ~
235 
236 "<html>\n" ~
237 "<head><title>400 No required SSL certificate was sent</title></head>\n" ~
238 "<body>\n" ~
239 "<center><h1>400 Bad Request</h1></center>\n" ~
240 "<hr><center>Hunt</center>\n" ~
241 "</body>\n" ~
242 "</html>\n";