EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.server.web]

COVERAGE SUMMARY FOR SOURCE FILE [WebThread.java]

nameclass, %method, %block, %line, %
WebThread.java100% (1/1)100% (15/15)86%  (897/1045)85%  (204.3/239)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class WebThread100% (1/1)100% (15/15)86%  (897/1045)85%  (204.3/239)
allow (): boolean 100% (1/1)47%  (8/17)33%  (2/6)
run (): void 100% (1/1)73%  (40/55)75%  (11.2/15)
parseHeader (): boolean 100% (1/1)75%  (191/256)78%  (48.7/62)
getAllowedFile (String): String 100% (1/1)83%  (10/12)80%  (4/5)
readHeaderLine (): String 100% (1/1)87%  (53/61)92%  (12/13)
process (): boolean 100% (1/1)88%  (353/399)88%  (65.5/74)
stopNow (): void 100% (1/1)89%  (8/9)80%  (4/5)
uploadMultipart (InputStream, int): void 100% (1/1)98%  (126/128)93%  (28/30)
WebThread (Socket, WebServer): void 100% (1/1)100% (14/14)100% (4/4)
adminShutdown (): String 100% (1/1)100% (5/5)100% (2/2)
getHeaderLineValue (String): String 100% (1/1)100% (9/9)100% (1/1)
join (int): void 100% (1/1)100% (6/6)100% (2/2)
parseAttributes (String): void 100% (1/1)100% (65/65)100% (16/16)
start (): void 100% (1/1)100% (4/4)100% (2/2)
trace (String): void 100% (1/1)100% (5/5)100% (2/2)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.server.web;
7 
8import java.io.BufferedInputStream;
9import java.io.BufferedOutputStream;
10import java.io.File;
11import java.io.FileOutputStream;
12import java.io.IOException;
13import java.io.InputStream;
14import java.io.OutputStream;
15import java.io.RandomAccessFile;
16import java.net.Socket;
17import java.net.UnknownHostException;
18import java.util.Iterator;
19import java.util.Locale;
20import java.util.Properties;
21import java.util.StringTokenizer;
22import org.h2.engine.Constants;
23import org.h2.engine.SysProperties;
24import org.h2.message.DbException;
25import org.h2.mvstore.DataUtils;
26import org.h2.util.IOUtils;
27import org.h2.util.NetUtils;
28import org.h2.util.StringUtils;
29 
30/**
31 * For each connection to a session, an object of this class is created.
32 * This class is used by the H2 Console.
33 */
34class WebThread extends WebApp implements Runnable {
35 
36    protected OutputStream output;
37    protected final Socket socket;
38    private final Thread thread;
39    private InputStream input;
40    private int headerBytes;
41    private String ifModifiedSince;
42 
43    WebThread(Socket socket, WebServer server) {
44        super(server);
45        this.socket = socket;
46        thread = new Thread(this, "H2 Console thread");
47    }
48 
49    /**
50     * Start the thread.
51     */
52    void start() {
53        thread.start();
54    }
55 
56    /**
57     * Wait until the thread is stopped.
58     *
59     * @param millis the maximum number of milliseconds to wait
60     */
61    void join(int millis) throws InterruptedException {
62        thread.join(millis);
63    }
64 
65    /**
66     * Close the connection now.
67     */
68    void stopNow() {
69        this.stop = true;
70        try {
71            socket.close();
72        } catch (IOException e) {
73            // ignore
74        }
75    }
76 
77    private String getAllowedFile(String requestedFile) {
78        if (!allow()) {
79            return "notAllowed.jsp";
80        }
81        if (requestedFile.length() == 0) {
82            return "index.do";
83        }
84        return requestedFile;
85    }
86 
87    @Override
88    public void run() {
89        try {
90            input = new BufferedInputStream(socket.getInputStream());
91            output = new BufferedOutputStream(socket.getOutputStream());
92            while (!stop) {
93                if (!process()) {
94                    break;
95                }
96            }
97        } catch (Exception e) {
98            DbException.traceThrowable(e);
99        }
100        IOUtils.closeSilently(output);
101        IOUtils.closeSilently(input);
102        try {
103            socket.close();
104        } catch (IOException e) {
105            // ignore
106        } finally {
107            server.remove(this);
108        }
109    }
110 
111    @SuppressWarnings("unchecked")
112    private boolean process() throws IOException {
113        boolean keepAlive = false;
114        String head = readHeaderLine();
115        if (head.startsWith("GET ") || head.startsWith("POST ")) {
116            int begin = head.indexOf('/'), end = head.lastIndexOf(' ');
117            String file;
118            if (begin < 0 || end < begin) {
119                file = "";
120            } else {
121                file = head.substring(begin + 1, end).trim();
122            }
123            trace(head + ": " + file);
124            file = getAllowedFile(file);
125            attributes = new Properties();
126            int paramIndex = file.indexOf("?");
127            session = null;
128            if (paramIndex >= 0) {
129                String attrib = file.substring(paramIndex + 1);
130                parseAttributes(attrib);
131                String sessionId = attributes.getProperty("jsessionid");
132                file = file.substring(0, paramIndex);
133                session = server.getSession(sessionId);
134            }
135            keepAlive = parseHeader();
136            String hostAddr = socket.getInetAddress().getHostAddress();
137            file = processRequest(file, hostAddr);
138            if (file.length() == 0) {
139                // asynchronous request
140                return true;
141            }
142            String message;
143            byte[] bytes;
144            if (cache && ifModifiedSince != null &&
145                    ifModifiedSince.equals(server.getStartDateTime())) {
146                bytes = null;
147                message = "HTTP/1.1 304 Not Modified\r\n";
148            } else {
149                bytes = server.getFile(file);
150                if (bytes == null) {
151                    message = "HTTP/1.1 404 Not Found\r\n";
152                    bytes = ("File not found: " + file).getBytes(Constants.UTF8);
153                    message += "Content-Length: " + bytes.length + "\r\n";
154                } else {
155                    if (session != null && file.endsWith(".jsp")) {
156                        String page = new String(bytes, Constants.UTF8);
157                        if (SysProperties.CONSOLE_STREAM) {
158                            Iterator<String> it = (Iterator<String>) session.map.remove("chunks");
159                            if (it != null) {
160                                message = "HTTP/1.1 200 OK\r\n";
161                                message += "Content-Type: " + mimeType + "\r\n";
162                                message += "Cache-Control: no-cache\r\n";
163                                message += "Transfer-Encoding: chunked\r\n";
164                                message += "\r\n";
165                                trace(message);
166                                output.write(message.getBytes());
167                                while (it.hasNext()) {
168                                    String s = it.next();
169                                    s = PageParser.parse(s, session.map);
170                                    bytes = s.getBytes(Constants.UTF8);
171                                    if (bytes.length == 0) {
172                                        continue;
173                                    }
174                                    output.write(Integer.toHexString(bytes.length).getBytes());
175                                    output.write("\r\n".getBytes());
176                                    output.write(bytes);
177                                    output.write("\r\n".getBytes());
178                                    output.flush();
179                                }
180                                output.write("0\r\n\r\n".getBytes());
181                                output.flush();
182                                return keepAlive;
183                            }
184                        }
185                        page = PageParser.parse(page, session.map);
186                        bytes = page.getBytes(Constants.UTF8);
187                    }
188                    message = "HTTP/1.1 200 OK\r\n";
189                    message += "Content-Type: " + mimeType + "\r\n";
190                    if (!cache) {
191                        message += "Cache-Control: no-cache\r\n";
192                    } else {
193                        message += "Cache-Control: max-age=10\r\n";
194                        message += "Last-Modified: " + server.getStartDateTime() + "\r\n";
195                    }
196                    message += "Content-Length: " + bytes.length + "\r\n";
197                }
198            }
199            message += "\r\n";
200            trace(message);
201            output.write(message.getBytes());
202            if (bytes != null) {
203                output.write(bytes);
204            }
205            output.flush();
206        }
207        return keepAlive;
208    }
209 
210    private String readHeaderLine() throws IOException {
211        StringBuilder buff = new StringBuilder();
212        while (true) {
213            headerBytes++;
214            int c = input.read();
215            if (c == -1) {
216                throw new IOException("Unexpected EOF");
217            } else if (c == '\r') {
218                headerBytes++;
219                if (input.read() == '\n') {
220                    return buff.length() > 0 ? buff.toString() : null;
221                }
222            } else if (c == '\n') {
223                return buff.length() > 0 ? buff.toString() : null;
224            } else {
225                buff.append((char) c);
226            }
227        }
228    }
229 
230    private void parseAttributes(String s) {
231        trace("data=" + s);
232        while (s != null) {
233            int idx = s.indexOf('=');
234            if (idx >= 0) {
235                String property = s.substring(0, idx);
236                s = s.substring(idx + 1);
237                idx = s.indexOf('&');
238                String value;
239                if (idx >= 0) {
240                    value = s.substring(0, idx);
241                    s = s.substring(idx + 1);
242                } else {
243                    value = s;
244                }
245                String attr = StringUtils.urlDecode(value);
246                attributes.put(property, attr);
247            } else {
248                break;
249            }
250        }
251        trace(attributes.toString());
252    }
253 
254    private boolean parseHeader() throws IOException {
255        boolean keepAlive = false;
256        trace("parseHeader");
257        int len = 0;
258        ifModifiedSince = null;
259        boolean multipart = false;
260        while (true) {
261            String line = readHeaderLine();
262            if (line == null) {
263                break;
264            }
265            trace(" " + line);
266            String lower = StringUtils.toLowerEnglish(line);
267            if (lower.startsWith("if-modified-since")) {
268                ifModifiedSince = getHeaderLineValue(line);
269            } else if (lower.startsWith("connection")) {
270                String conn = getHeaderLineValue(line);
271                if ("keep-alive".equals(conn)) {
272                    keepAlive = true;
273                }
274            } else if (lower.startsWith("content-type")) {
275                String type = getHeaderLineValue(line);
276                if (type.startsWith("multipart/form-data")) {
277                    multipart = true;
278                }
279            } else if (lower.startsWith("content-length")) {
280                len = Integer.parseInt(getHeaderLineValue(line));
281                trace("len=" + len);
282            } else if (lower.startsWith("user-agent")) {
283                boolean isWebKit = lower.contains("webkit/");
284                if (isWebKit && session != null) {
285                    // workaround for what seems to be a WebKit bug:
286                    // http://code.google.com/p/chromium/issues/detail?id=6402
287                    session.put("frame-border", "1");
288                    session.put("frameset-border", "2");
289                }
290            } else if (lower.startsWith("accept-language")) {
291                Locale locale = session == null ? null : session.locale;
292                if (locale == null) {
293                    String languages = getHeaderLineValue(line);
294                    StringTokenizer tokenizer = new StringTokenizer(languages, ",;");
295                    while (tokenizer.hasMoreTokens()) {
296                        String token = tokenizer.nextToken();
297                        if (!token.startsWith("q=")) {
298                            if (server.supportsLanguage(token)) {
299                                int dash = token.indexOf('-');
300                                if (dash >= 0) {
301                                    String language = token.substring(0, dash);
302                                    String country = token.substring(dash + 1);
303                                    locale = new Locale(language, country);
304                                } else {
305                                    locale = new Locale(token, "");
306                                }
307                                headerLanguage = locale.getLanguage();
308                                if (session != null) {
309                                    session.locale = locale;
310                                    session.put("language", headerLanguage);
311                                    server.readTranslations(session, headerLanguage);
312                                }
313                                break;
314                            }
315                        }
316                    }
317                }
318            } else if (line.trim().length() == 0) {
319                break;
320            }
321        }
322        if (multipart) {
323            uploadMultipart(input, len);
324        } else if (session != null && len > 0) {
325            byte[] bytes = DataUtils.newBytes(len);
326            for (int pos = 0; pos < len;) {
327                pos += input.read(bytes, pos, len - pos);
328            }
329            String s = new String(bytes);
330            parseAttributes(s);
331        }
332        return keepAlive;
333    }
334 
335    private void uploadMultipart(InputStream in, int len) throws IOException {
336        if (!new File(WebServer.TRANSFER).exists()) {
337            return;
338        }
339        String fileName = "temp.bin";
340        headerBytes = 0;
341        String boundary = readHeaderLine();
342        while (true) {
343            String line = readHeaderLine();
344            if (line == null) {
345                break;
346            }
347            int index = line.indexOf("filename=\"");
348            if (index > 0) {
349                fileName = line.substring(index +
350                        "filename=\"".length(), line.lastIndexOf('"'));
351            }
352            trace(" " + line);
353        }
354        if (!WebServer.isSimpleName(fileName)) {
355            return;
356        }
357        len -= headerBytes;
358        File file = new File(WebServer.TRANSFER, fileName);
359        OutputStream out = new FileOutputStream(file);
360        IOUtils.copy(in, out, len);
361        out.close();
362        // remove the boundary
363        RandomAccessFile f = new RandomAccessFile(file, "rw");
364        int testSize = (int) Math.min(f.length(), Constants.IO_BUFFER_SIZE);
365        f.seek(f.length() - testSize);
366        byte[] bytes = DataUtils.newBytes(Constants.IO_BUFFER_SIZE);
367        f.readFully(bytes, 0, testSize);
368        String s = new String(bytes, "ASCII");
369        int x = s.lastIndexOf(boundary);
370        f.setLength(f.length() - testSize + x - 2);
371        f.close();
372    }
373 
374    private static String getHeaderLineValue(String line) {
375        return line.substring(line.indexOf(':') + 1).trim();
376    }
377 
378    @Override
379    protected String adminShutdown() {
380        stopNow();
381        return super.adminShutdown();
382    }
383 
384    private boolean allow() {
385        if (server.getAllowOthers()) {
386            return true;
387        }
388        try {
389            return NetUtils.isLocalAddress(socket);
390        } catch (UnknownHostException e) {
391            server.traceError(e);
392            return false;
393        }
394    }
395 
396    private void trace(String s) {
397        server.trace(s);
398    }
399}

[all classes][org.h2.server.web]
EMMA 2.0.5312 (C) Vladimir Roubtsov