PageRenderTime 58ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/extern/nanohttpd/core/src/main/java/fi/iki/elonen/NanoHTTPD.java

https://gitlab.com/paresh/fdroidclient
Java | 1288 lines | 837 code | 137 blank | 314 comment | 150 complexity | 81c3d0c24192d4bd04cb1cb418dab1a0 MD5 | raw file
  1. package fi.iki.elonen;
  2. import java.io.BufferedReader;
  3. import java.io.ByteArrayInputStream;
  4. import java.io.Closeable;
  5. import java.io.File;
  6. import java.io.FileInputStream;
  7. import java.io.FileOutputStream;
  8. import java.io.IOException;
  9. import java.io.InputStream;
  10. import java.io.InputStreamReader;
  11. import java.io.OutputStream;
  12. import java.io.PrintWriter;
  13. import java.io.RandomAccessFile;
  14. import java.io.PushbackInputStream;
  15. import java.io.UnsupportedEncodingException;
  16. import java.net.InetAddress;
  17. import java.net.InetSocketAddress;
  18. import java.net.ServerSocket;
  19. import java.net.Socket;
  20. import java.net.SocketException;
  21. import java.net.SocketTimeoutException;
  22. import java.net.URLDecoder;
  23. import java.nio.ByteBuffer;
  24. import java.nio.channels.FileChannel;
  25. import java.text.SimpleDateFormat;
  26. import java.util.ArrayList;
  27. import java.util.Calendar;
  28. import java.util.Date;
  29. import java.util.HashMap;
  30. import java.util.HashSet;
  31. import java.util.Iterator;
  32. import java.util.List;
  33. import java.util.Locale;
  34. import java.util.Map;
  35. import java.util.Set;
  36. import java.util.StringTokenizer;
  37. import java.util.TimeZone;
  38. import java.security.KeyStore;
  39. import javax.net.ssl.*;
  40. /**
  41. * A simple, tiny, nicely embeddable HTTP server in Java
  42. * <p/>
  43. * <p/>
  44. * NanoHTTPD
  45. * <p></p>Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias</p>
  46. * <p/>
  47. * <p/>
  48. * <b>Features + limitations: </b>
  49. * <ul>
  50. * <p/>
  51. * <li>Only one Java file</li>
  52. * <li>Java 5 compatible</li>
  53. * <li>Released as open source, Modified BSD licence</li>
  54. * <li>No fixed config files, logging, authorization etc. (Implement yourself if you need them.)</li>
  55. * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT support in 1.25)</li>
  56. * <li>Supports both dynamic content and file serving</li>
  57. * <li>Supports file upload (since version 1.2, 2010)</li>
  58. * <li>Supports partial content (streaming)</li>
  59. * <li>Supports ETags</li>
  60. * <li>Never caches anything</li>
  61. * <li>Doesn't limit bandwidth, request time or simultaneous connections</li>
  62. * <li>Default code serves files and shows all HTTP parameters and headers</li>
  63. * <li>File server supports directory listing, index.html and index.htm</li>
  64. * <li>File server supports partial content (streaming)</li>
  65. * <li>File server supports ETags</li>
  66. * <li>File server does the 301 redirection trick for directories without '/'</li>
  67. * <li>File server supports simple skipping for files (continue download)</li>
  68. * <li>File server serves also very long files without memory overhead</li>
  69. * <li>Contains a built-in list of most common mime types</li>
  70. * <li>All header names are converted lowercase so they don't vary between browsers/clients</li>
  71. * <p/>
  72. * </ul>
  73. * <p/>
  74. * <p/>
  75. * <b>How to use: </b>
  76. * <ul>
  77. * <p/>
  78. * <li>Subclass and implement serve() and embed to your own program</li>
  79. * <p/>
  80. * </ul>
  81. * <p/>
  82. * See the separate "LICENSE.md" file for the distribution license (Modified BSD licence)
  83. */
  84. public abstract class NanoHTTPD {
  85. /**
  86. * Maximum time to wait on Socket.getInputStream().read() (in milliseconds)
  87. * This is required as the Keep-Alive HTTP connections would otherwise
  88. * block the socket reading thread forever (or as long the browser is open).
  89. */
  90. public static final int SOCKET_READ_TIMEOUT = 5000;
  91. /**
  92. * Common mime type for dynamic content: plain text
  93. */
  94. public static final String MIME_PLAINTEXT = "text/plain";
  95. /**
  96. * Common mime type for dynamic content: html
  97. */
  98. public static final String MIME_HTML = "text/html";
  99. /**
  100. * Pseudo-Parameter to use to store the actual query string in the parameters map for later re-processing.
  101. */
  102. private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";
  103. private final String hostname;
  104. private final int myPort;
  105. private ServerSocket myServerSocket;
  106. private Set<Socket> openConnections = new HashSet<Socket>();
  107. private SSLServerSocketFactory sslServerSocketFactory;
  108. private Thread myThread;
  109. /**
  110. * Pluggable strategy for asynchronously executing requests.
  111. */
  112. private AsyncRunner asyncRunner;
  113. /**
  114. * Pluggable strategy for creating and cleaning up temporary files.
  115. */
  116. private TempFileManagerFactory tempFileManagerFactory;
  117. /**
  118. * Constructs an HTTP server on given port.
  119. */
  120. public NanoHTTPD(int port) {
  121. this(null, port);
  122. }
  123. /**
  124. * Constructs an HTTP server on given hostname and port.
  125. */
  126. public NanoHTTPD(String hostname, int port) {
  127. this.hostname = hostname;
  128. this.myPort = port;
  129. setTempFileManagerFactory(new DefaultTempFileManagerFactory());
  130. setAsyncRunner(new DefaultAsyncRunner());
  131. }
  132. private static final void safeClose(Closeable closeable) {
  133. if (closeable != null) {
  134. try {
  135. closeable.close();
  136. } catch (IOException e) {
  137. }
  138. }
  139. }
  140. private static final void safeClose(Socket closeable) {
  141. if (closeable != null) {
  142. try {
  143. closeable.close();
  144. } catch (IOException e) {
  145. }
  146. }
  147. }
  148. private static final void safeClose(ServerSocket closeable) {
  149. if (closeable != null) {
  150. try {
  151. closeable.close();
  152. } catch (IOException e) {
  153. }
  154. }
  155. }
  156. /**
  157. * Creates an SSLSocketFactory for HTTPS.
  158. *
  159. * Pass a KeyStore resource with your certificate and passphrase
  160. */
  161. public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException {
  162. SSLServerSocketFactory res = null;
  163. try {
  164. KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
  165. InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath);
  166. keystore.load(keystoreStream, passphrase);
  167. TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  168. trustManagerFactory.init(keystore);
  169. KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  170. keyManagerFactory.init(keystore, passphrase);
  171. SSLContext ctx = SSLContext.getInstance("TLS");
  172. ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
  173. res = ctx.getServerSocketFactory();
  174. } catch (Exception e) {
  175. throw new IOException(e.getMessage());
  176. }
  177. return res;
  178. }
  179. /**
  180. * Creates an SSLSocketFactory for HTTPS.
  181. *
  182. * Pass a loaded KeyStore and a loaded KeyManagerFactory.
  183. * These objects must properly loaded/initialized by the caller.
  184. */
  185. public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManagerFactory loadedKeyFactory) throws IOException {
  186. SSLServerSocketFactory res = null;
  187. try {
  188. TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  189. trustManagerFactory.init(loadedKeyStore);
  190. SSLContext ctx = SSLContext.getInstance("TLS");
  191. ctx.init(loadedKeyFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
  192. res = ctx.getServerSocketFactory();
  193. } catch (Exception e) {
  194. throw new IOException(e.getMessage());
  195. }
  196. return res;
  197. }
  198. /**
  199. * Creates an SSLSocketFactory for HTTPS.
  200. *
  201. * Pass a loaded KeyStore and an array of loaded KeyManagers.
  202. * These objects must properly loaded/initialized by the caller.
  203. */
  204. public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManager[] keyManagers) throws IOException {
  205. SSLServerSocketFactory res = null;
  206. try {
  207. TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  208. trustManagerFactory.init(loadedKeyStore);
  209. SSLContext ctx = SSLContext.getInstance("TLS");
  210. ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null);
  211. res = ctx.getServerSocketFactory();
  212. } catch (Exception e) {
  213. throw new IOException(e.getMessage());
  214. }
  215. return res;
  216. }
  217. /**
  218. * Call before start() to serve over HTTPS instead of HTTP
  219. */
  220. public void makeSecure(SSLServerSocketFactory sslServerSocketFactory) {
  221. this.sslServerSocketFactory = sslServerSocketFactory;
  222. }
  223. /**
  224. * Start the server.
  225. *
  226. * @throws IOException if the socket is in use.
  227. */
  228. public void start() throws IOException {
  229. if (sslServerSocketFactory != null) {
  230. SSLServerSocket ss = (SSLServerSocket) sslServerSocketFactory.createServerSocket();
  231. ss.setNeedClientAuth(false);
  232. myServerSocket = ss;
  233. } else {
  234. myServerSocket = new ServerSocket();
  235. }
  236. myServerSocket.bind((hostname != null) ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));
  237. myThread = new Thread(new Runnable() {
  238. @Override
  239. public void run() {
  240. do {
  241. try {
  242. final Socket finalAccept = myServerSocket.accept();
  243. registerConnection(finalAccept);
  244. finalAccept.setSoTimeout(SOCKET_READ_TIMEOUT);
  245. final InputStream inputStream = finalAccept.getInputStream();
  246. asyncRunner.exec(new Runnable() {
  247. @Override
  248. public void run() {
  249. OutputStream outputStream = null;
  250. try {
  251. outputStream = finalAccept.getOutputStream();
  252. TempFileManager tempFileManager = tempFileManagerFactory.create();
  253. HTTPSession session = new HTTPSession(tempFileManager, inputStream, outputStream, finalAccept.getInetAddress());
  254. while (!finalAccept.isClosed()) {
  255. session.execute();
  256. }
  257. } catch (Exception e) {
  258. // When the socket is closed by the client, we throw our own SocketException
  259. // to break the "keep alive" loop above. If the exception was anything other
  260. // than the expected SocketException OR a SocketTimeoutException, print the
  261. // stacktrace
  262. if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage())) &&
  263. !(e instanceof SocketTimeoutException)) {
  264. e.printStackTrace();
  265. }
  266. } finally {
  267. safeClose(outputStream);
  268. safeClose(inputStream);
  269. safeClose(finalAccept);
  270. unRegisterConnection(finalAccept);
  271. }
  272. }
  273. });
  274. } catch (IOException e) {
  275. }
  276. } while (!myServerSocket.isClosed());
  277. }
  278. });
  279. myThread.setDaemon(true);
  280. myThread.setName("NanoHttpd Main Listener");
  281. myThread.start();
  282. }
  283. /**
  284. * Stop the server.
  285. */
  286. public void stop() {
  287. try {
  288. safeClose(myServerSocket);
  289. closeAllConnections();
  290. myThread.join();
  291. } catch (Exception e) {
  292. e.printStackTrace();
  293. }
  294. }
  295. /**
  296. * Registers that a new connection has been set up.
  297. *
  298. * @param socket
  299. * the {@link Socket} for the connection.
  300. */
  301. public synchronized void registerConnection(Socket socket) {
  302. openConnections.add(socket);
  303. }
  304. /**
  305. * Registers that a connection has been closed
  306. *
  307. * @param socket
  308. * the {@link Socket} for the connection.
  309. */
  310. public synchronized void unRegisterConnection(Socket socket) {
  311. openConnections.remove(socket);
  312. }
  313. /**
  314. * Forcibly closes all connections that are open.
  315. */
  316. public synchronized void closeAllConnections() {
  317. for (Socket socket : openConnections) {
  318. safeClose(socket);
  319. }
  320. }
  321. public final int getListeningPort() {
  322. return myServerSocket == null ? -1 : myServerSocket.getLocalPort();
  323. }
  324. public final boolean wasStarted() {
  325. return myServerSocket != null && myThread != null;
  326. }
  327. public final boolean isAlive() {
  328. return wasStarted() && !myServerSocket.isClosed() && myThread.isAlive();
  329. }
  330. /**
  331. * Override this to customize the server.
  332. * <p/>
  333. * <p/>
  334. * (By default, this delegates to serveFile() and allows directory listing.)
  335. *
  336. * @param uri Percent-decoded URI without parameters, for example "/index.cgi"
  337. * @param method "GET", "POST" etc.
  338. * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data.
  339. * @param headers Header entries, percent decoded
  340. * @return HTTP response, see class Response for details
  341. */
  342. @Deprecated
  343. public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms,
  344. Map<String, String> files) {
  345. return new Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found");
  346. }
  347. /**
  348. * Override this to customize the server.
  349. * <p/>
  350. * <p/>
  351. * (By default, this delegates to serveFile() and allows directory listing.)
  352. *
  353. * @param session The HTTP session
  354. * @return HTTP response, see class Response for details
  355. */
  356. public Response serve(IHTTPSession session) {
  357. Map<String, String> files = new HashMap<String, String>();
  358. Method method = session.getMethod();
  359. if (Method.PUT.equals(method) || Method.POST.equals(method)) {
  360. try {
  361. session.parseBody(files);
  362. } catch (IOException ioe) {
  363. return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
  364. } catch (ResponseException re) {
  365. return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
  366. }
  367. }
  368. Map<String, String> parms = session.getParms();
  369. parms.put(QUERY_STRING_PARAMETER, session.getQueryParameterString());
  370. return serve(session.getUri(), method, session.getHeaders(), parms, files);
  371. }
  372. /**
  373. * Decode percent encoded <code>String</code> values.
  374. *
  375. * @param str the percent encoded <code>String</code>
  376. * @return expanded form of the input, for example "foo%20bar" becomes "foo bar"
  377. */
  378. protected String decodePercent(String str) {
  379. String decoded = null;
  380. try {
  381. decoded = URLDecoder.decode(str, "UTF8");
  382. } catch (UnsupportedEncodingException ignored) {
  383. }
  384. return decoded;
  385. }
  386. /**
  387. * Decode parameters from a URL, handing the case where a single parameter name might have been
  388. * supplied several times, by return lists of values. In general these lists will contain a single
  389. * element.
  390. *
  391. * @param parms original <b>NanoHttpd</b> parameters values, as passed to the <code>serve()</code> method.
  392. * @return a map of <code>String</code> (parameter name) to <code>List&lt;String&gt;</code> (a list of the values supplied).
  393. */
  394. protected Map<String, List<String>> decodeParameters(Map<String, String> parms) {
  395. return this.decodeParameters(parms.get(QUERY_STRING_PARAMETER));
  396. }
  397. /**
  398. * Decode parameters from a URL, handing the case where a single parameter name might have been
  399. * supplied several times, by return lists of values. In general these lists will contain a single
  400. * element.
  401. *
  402. * @param queryString a query string pulled from the URL.
  403. * @return a map of <code>String</code> (parameter name) to <code>List&lt;String&gt;</code> (a list of the values supplied).
  404. */
  405. protected Map<String, List<String>> decodeParameters(String queryString) {
  406. Map<String, List<String>> parms = new HashMap<String, List<String>>();
  407. if (queryString != null) {
  408. StringTokenizer st = new StringTokenizer(queryString, "&");
  409. while (st.hasMoreTokens()) {
  410. String e = st.nextToken();
  411. int sep = e.indexOf('=');
  412. String propertyName = (sep >= 0) ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim();
  413. if (!parms.containsKey(propertyName)) {
  414. parms.put(propertyName, new ArrayList<String>());
  415. }
  416. String propertyValue = (sep >= 0) ? decodePercent(e.substring(sep + 1)) : null;
  417. if (propertyValue != null) {
  418. parms.get(propertyName).add(propertyValue);
  419. }
  420. }
  421. }
  422. return parms;
  423. }
  424. // ------------------------------------------------------------------------------- //
  425. //
  426. // Threading Strategy.
  427. //
  428. // ------------------------------------------------------------------------------- //
  429. /**
  430. * Pluggable strategy for asynchronously executing requests.
  431. *
  432. * @param asyncRunner new strategy for handling threads.
  433. */
  434. public void setAsyncRunner(AsyncRunner asyncRunner) {
  435. this.asyncRunner = asyncRunner;
  436. }
  437. // ------------------------------------------------------------------------------- //
  438. //
  439. // Temp file handling strategy.
  440. //
  441. // ------------------------------------------------------------------------------- //
  442. /**
  443. * Pluggable strategy for creating and cleaning up temporary files.
  444. *
  445. * @param tempFileManagerFactory new strategy for handling temp files.
  446. */
  447. public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) {
  448. this.tempFileManagerFactory = tempFileManagerFactory;
  449. }
  450. /**
  451. * HTTP Request methods, with the ability to decode a <code>String</code> back to its enum value.
  452. */
  453. public enum Method {
  454. GET, PUT, POST, DELETE, HEAD, OPTIONS;
  455. static Method lookup(String method) {
  456. for (Method m : Method.values()) {
  457. if (m.toString().equalsIgnoreCase(method)) {
  458. return m;
  459. }
  460. }
  461. return null;
  462. }
  463. }
  464. /**
  465. * Pluggable strategy for asynchronously executing requests.
  466. */
  467. public interface AsyncRunner {
  468. void exec(Runnable code);
  469. }
  470. /**
  471. * Factory to create temp file managers.
  472. */
  473. public interface TempFileManagerFactory {
  474. TempFileManager create();
  475. }
  476. // ------------------------------------------------------------------------------- //
  477. /**
  478. * Temp file manager.
  479. * <p/>
  480. * <p>Temp file managers are created 1-to-1 with incoming requests, to create and cleanup
  481. * temporary files created as a result of handling the request.</p>
  482. */
  483. public interface TempFileManager {
  484. TempFile createTempFile() throws Exception;
  485. void clear();
  486. }
  487. /**
  488. * A temp file.
  489. * <p/>
  490. * <p>Temp files are responsible for managing the actual temporary storage and cleaning
  491. * themselves up when no longer needed.</p>
  492. */
  493. public interface TempFile {
  494. OutputStream open() throws Exception;
  495. void delete() throws Exception;
  496. String getName();
  497. }
  498. /**
  499. * Default threading strategy for NanoHttpd.
  500. * <p/>
  501. * <p>By default, the server spawns a new Thread for every incoming request. These are set
  502. * to <i>daemon</i> status, and named according to the request number. The name is
  503. * useful when profiling the application.</p>
  504. */
  505. public static class DefaultAsyncRunner implements AsyncRunner {
  506. private long requestCount;
  507. @Override
  508. public void exec(Runnable code) {
  509. ++requestCount;
  510. Thread t = new Thread(code);
  511. t.setDaemon(true);
  512. t.setName("NanoHttpd Request Processor (#" + requestCount + ")");
  513. t.start();
  514. }
  515. }
  516. /**
  517. * Default strategy for creating and cleaning up temporary files.
  518. * <p/>
  519. * <p></p>This class stores its files in the standard location (that is,
  520. * wherever <code>java.io.tmpdir</code> points to). Files are added
  521. * to an internal list, and deleted when no longer needed (that is,
  522. * when <code>clear()</code> is invoked at the end of processing a
  523. * request).</p>
  524. */
  525. public static class DefaultTempFileManager implements TempFileManager {
  526. private final String tmpdir;
  527. private final List<TempFile> tempFiles;
  528. public DefaultTempFileManager() {
  529. tmpdir = System.getProperty("java.io.tmpdir");
  530. tempFiles = new ArrayList<TempFile>();
  531. }
  532. @Override
  533. public TempFile createTempFile() throws Exception {
  534. DefaultTempFile tempFile = new DefaultTempFile(tmpdir);
  535. tempFiles.add(tempFile);
  536. return tempFile;
  537. }
  538. @Override
  539. public void clear() {
  540. for (TempFile file : tempFiles) {
  541. try {
  542. file.delete();
  543. } catch (Exception ignored) {
  544. }
  545. }
  546. tempFiles.clear();
  547. }
  548. }
  549. /**
  550. * Default strategy for creating and cleaning up temporary files.
  551. * <p/>
  552. * <p></p></[>By default, files are created by <code>File.createTempFile()</code> in
  553. * the directory specified.</p>
  554. */
  555. public static class DefaultTempFile implements TempFile {
  556. private File file;
  557. private OutputStream fstream;
  558. public DefaultTempFile(String tempdir) throws IOException {
  559. file = File.createTempFile("NanoHTTPD-", "", new File(tempdir));
  560. fstream = new FileOutputStream(file);
  561. }
  562. @Override
  563. public OutputStream open() throws Exception {
  564. return fstream;
  565. }
  566. @Override
  567. public void delete() throws Exception {
  568. safeClose(fstream);
  569. file.delete();
  570. }
  571. @Override
  572. public String getName() {
  573. return file.getAbsolutePath();
  574. }
  575. }
  576. /**
  577. * HTTP response. Return one of these from serve().
  578. */
  579. public static class Response {
  580. /**
  581. * HTTP status code after processing, e.g. "200 OK", HTTP_OK
  582. */
  583. private IStatus status;
  584. /**
  585. * MIME type of content, e.g. "text/html"
  586. */
  587. private String mimeType;
  588. /**
  589. * Data of the response, may be null.
  590. */
  591. private InputStream data;
  592. /**
  593. * Headers for the HTTP response. Use addHeader() to add lines.
  594. */
  595. private Map<String, String> header = new HashMap<String, String>();
  596. /**
  597. * The request method that spawned this response.
  598. */
  599. private Method requestMethod;
  600. /**
  601. * Use chunkedTransfer
  602. */
  603. private boolean chunkedTransfer;
  604. /**
  605. * Default constructor: response = HTTP_OK, mime = MIME_HTML and your supplied message
  606. */
  607. public Response(String msg) {
  608. this(Status.OK, MIME_HTML, msg);
  609. }
  610. /**
  611. * Basic constructor.
  612. */
  613. public Response(IStatus status, String mimeType, InputStream data) {
  614. this.status = status;
  615. this.mimeType = mimeType;
  616. this.data = data;
  617. }
  618. /**
  619. * Convenience method that makes an InputStream out of given text.
  620. */
  621. public Response(IStatus status, String mimeType, String txt) {
  622. this.status = status;
  623. this.mimeType = mimeType;
  624. try {
  625. this.data = txt != null ? new ByteArrayInputStream(txt.getBytes("UTF-8")) : null;
  626. } catch (java.io.UnsupportedEncodingException uee) {
  627. uee.printStackTrace();
  628. }
  629. }
  630. /**
  631. * Adds given line to the header.
  632. */
  633. public void addHeader(String name, String value) {
  634. header.put(name, value);
  635. }
  636. /**
  637. * Sends given response to the socket.
  638. */
  639. protected void send(OutputStream outputStream) {
  640. String mime = mimeType;
  641. SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
  642. gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
  643. try {
  644. if (status == null) {
  645. throw new Error("sendResponse(): Status can't be null.");
  646. }
  647. PrintWriter pw = new PrintWriter(outputStream);
  648. pw.print("HTTP/1.1 " + status.getDescription() + " \r\n");
  649. if (mime != null) {
  650. pw.print("Content-Type: " + mime + "\r\n");
  651. }
  652. if (header == null || header.get("Date") == null) {
  653. pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");
  654. }
  655. if (header != null) {
  656. for (String key : header.keySet()) {
  657. String value = header.get(key);
  658. pw.print(key + ": " + value + "\r\n");
  659. }
  660. }
  661. sendConnectionHeaderIfNotAlreadyPresent(pw, header);
  662. if (requestMethod != Method.HEAD && chunkedTransfer) {
  663. sendAsChunked(outputStream, pw);
  664. } else {
  665. sendAsFixedLength(outputStream, pw);
  666. }
  667. outputStream.flush();
  668. safeClose(data);
  669. } catch (IOException ioe) {
  670. // Couldn't write? No can do.
  671. }
  672. }
  673. protected void sendConnectionHeaderIfNotAlreadyPresent(PrintWriter pw, Map<String, String> header) {
  674. boolean connectionAlreadySent = false;
  675. for (String headerName : header.keySet()) {
  676. connectionAlreadySent |= headerName.equalsIgnoreCase("connection");
  677. }
  678. if (!connectionAlreadySent) {
  679. pw.print("Connection: keep-alive\r\n");
  680. }
  681. }
  682. private void sendAsChunked(OutputStream outputStream, PrintWriter pw) throws IOException {
  683. pw.print("Transfer-Encoding: chunked\r\n");
  684. pw.print("\r\n");
  685. pw.flush();
  686. int BUFFER_SIZE = 16 * 1024;
  687. byte[] CRLF = "\r\n".getBytes();
  688. byte[] buff = new byte[BUFFER_SIZE];
  689. int read;
  690. while ((read = data.read(buff)) > 0) {
  691. outputStream.write(String.format("%x\r\n", read).getBytes());
  692. outputStream.write(buff, 0, read);
  693. outputStream.write(CRLF);
  694. }
  695. outputStream.write(String.format("0\r\n\r\n").getBytes());
  696. }
  697. private void sendAsFixedLength(OutputStream outputStream, PrintWriter pw) throws IOException {
  698. int pending = data != null ? data.available() : 0; // This is to support partial sends, see serveFile()
  699. pw.print("Content-Length: "+pending+"\r\n");
  700. pw.print("\r\n");
  701. pw.flush();
  702. if (requestMethod != Method.HEAD && data != null) {
  703. int BUFFER_SIZE = 16 * 1024;
  704. byte[] buff = new byte[BUFFER_SIZE];
  705. while (pending > 0) {
  706. int read = data.read(buff, 0, ((pending > BUFFER_SIZE) ? BUFFER_SIZE : pending));
  707. if (read <= 0) {
  708. break;
  709. }
  710. outputStream.write(buff, 0, read);
  711. pending -= read;
  712. }
  713. }
  714. }
  715. public IStatus getStatus() {
  716. return status;
  717. }
  718. public void setStatus(Status status) {
  719. this.status = status;
  720. }
  721. public String getMimeType() {
  722. return mimeType;
  723. }
  724. public void setMimeType(String mimeType) {
  725. this.mimeType = mimeType;
  726. }
  727. public InputStream getData() {
  728. return data;
  729. }
  730. public void setData(InputStream data) {
  731. this.data = data;
  732. }
  733. public Method getRequestMethod() {
  734. return requestMethod;
  735. }
  736. public void setRequestMethod(Method requestMethod) {
  737. this.requestMethod = requestMethod;
  738. }
  739. public void setChunkedTransfer(boolean chunkedTransfer) {
  740. this.chunkedTransfer = chunkedTransfer;
  741. }
  742. public interface IStatus {
  743. int getRequestStatus();
  744. String getDescription();
  745. }
  746. /**
  747. * Some HTTP response status codes
  748. */
  749. public enum Status implements IStatus {
  750. SWITCH_PROTOCOL(101, "Switching Protocols"), OK(200, "OK"), CREATED(201, "Created"), ACCEPTED(202, "Accepted"), NO_CONTENT(204, "No Content"), PARTIAL_CONTENT(206, "Partial Content"), REDIRECT(301,
  751. "Moved Permanently"), NOT_MODIFIED(304, "Not Modified"), BAD_REQUEST(400, "Bad Request"), UNAUTHORIZED(401,
  752. "Unauthorized"), FORBIDDEN(403, "Forbidden"), NOT_FOUND(404, "Not Found"), METHOD_NOT_ALLOWED(405, "Method Not Allowed"), RANGE_NOT_SATISFIABLE(416,
  753. "Requested Range Not Satisfiable"), INTERNAL_ERROR(500, "Internal Server Error");
  754. private final int requestStatus;
  755. private final String description;
  756. Status(int requestStatus, String description) {
  757. this.requestStatus = requestStatus;
  758. this.description = description;
  759. }
  760. @Override
  761. public int getRequestStatus() {
  762. return this.requestStatus;
  763. }
  764. @Override
  765. public String getDescription() {
  766. return "" + this.requestStatus + " " + description;
  767. }
  768. }
  769. }
  770. public static final class ResponseException extends Exception {
  771. private final Response.Status status;
  772. public ResponseException(Response.Status status, String message) {
  773. super(message);
  774. this.status = status;
  775. }
  776. public ResponseException(Response.Status status, String message, Exception e) {
  777. super(message, e);
  778. this.status = status;
  779. }
  780. public Response.Status getStatus() {
  781. return status;
  782. }
  783. }
  784. /**
  785. * Default strategy for creating and cleaning up temporary files.
  786. */
  787. private class DefaultTempFileManagerFactory implements TempFileManagerFactory {
  788. @Override
  789. public TempFileManager create() {
  790. return new DefaultTempFileManager();
  791. }
  792. }
  793. /**
  794. * Handles one session, i.e. parses the HTTP request and returns the response.
  795. */
  796. public interface IHTTPSession {
  797. void execute() throws IOException;
  798. Map<String, String> getParms();
  799. Map<String, String> getHeaders();
  800. /**
  801. * @return the path part of the URL.
  802. */
  803. String getUri();
  804. String getQueryParameterString();
  805. Method getMethod();
  806. InputStream getInputStream();
  807. CookieHandler getCookies();
  808. /**
  809. * Adds the files in the request body to the files map.
  810. * @arg files - map to modify
  811. */
  812. void parseBody(Map<String, String> files) throws IOException, ResponseException;
  813. }
  814. protected class HTTPSession implements IHTTPSession {
  815. public static final int BUFSIZE = 8192;
  816. private final TempFileManager tempFileManager;
  817. private final OutputStream outputStream;
  818. private PushbackInputStream inputStream;
  819. private int splitbyte;
  820. private int rlen;
  821. private String uri;
  822. private Method method;
  823. private Map<String, String> parms;
  824. private Map<String, String> headers;
  825. private CookieHandler cookies;
  826. private String queryParameterString;
  827. public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {
  828. this.tempFileManager = tempFileManager;
  829. this.inputStream = new PushbackInputStream(inputStream, BUFSIZE);
  830. this.outputStream = outputStream;
  831. }
  832. public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) {
  833. this.tempFileManager = tempFileManager;
  834. this.inputStream = new PushbackInputStream(inputStream, BUFSIZE);
  835. this.outputStream = outputStream;
  836. String remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1" : inetAddress.getHostAddress().toString();
  837. headers = new HashMap<String, String>();
  838. headers.put("remote-addr", remoteIp);
  839. headers.put("http-client-ip", remoteIp);
  840. }
  841. @Override
  842. public void execute() throws IOException {
  843. try {
  844. // Read the first 8192 bytes.
  845. // The full header should fit in here.
  846. // Apache's default header limit is 8KB.
  847. // Do NOT assume that a single read will get the entire header at once!
  848. byte[] buf = new byte[BUFSIZE];
  849. splitbyte = 0;
  850. rlen = 0;
  851. {
  852. int read = -1;
  853. try {
  854. read = inputStream.read(buf, 0, BUFSIZE);
  855. } catch (Exception e) {
  856. safeClose(inputStream);
  857. safeClose(outputStream);
  858. throw new SocketException("NanoHttpd Shutdown");
  859. }
  860. if (read == -1) {
  861. // socket was been closed
  862. safeClose(inputStream);
  863. safeClose(outputStream);
  864. throw new SocketException("NanoHttpd Shutdown");
  865. }
  866. while (read > 0) {
  867. rlen += read;
  868. splitbyte = findHeaderEnd(buf, rlen);
  869. if (splitbyte > 0)
  870. break;
  871. read = inputStream.read(buf, rlen, BUFSIZE - rlen);
  872. }
  873. }
  874. if (splitbyte < rlen) {
  875. inputStream.unread(buf, splitbyte, rlen - splitbyte);
  876. }
  877. parms = new HashMap<String, String>();
  878. if(null == headers) {
  879. headers = new HashMap<String, String>();
  880. }
  881. // Create a BufferedReader for parsing the header.
  882. BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, rlen)));
  883. // Decode the header into parms and header java properties
  884. Map<String, String> pre = new HashMap<String, String>();
  885. decodeHeader(hin, pre, parms, headers);
  886. method = Method.lookup(pre.get("method"));
  887. if (method == null) {
  888. throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error.");
  889. }
  890. uri = pre.get("uri");
  891. cookies = new CookieHandler(headers);
  892. // Ok, now do the serve()
  893. Response r = serve(this);
  894. if (r == null) {
  895. throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
  896. } else {
  897. cookies.unloadQueue(r);
  898. r.setRequestMethod(method);
  899. r.send(outputStream);
  900. }
  901. } catch (SocketException e) {
  902. // throw it out to close socket object (finalAccept)
  903. throw e;
  904. } catch (SocketTimeoutException ste) {
  905. // treat socket timeouts the same way we treat socket exceptions
  906. // i.e. close the stream & finalAccept object by throwing the
  907. // exception up the call stack.
  908. throw ste;
  909. } catch (IOException ioe) {
  910. Response r = new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
  911. r.send(outputStream);
  912. safeClose(outputStream);
  913. } catch (ResponseException re) {
  914. Response r = new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
  915. r.send(outputStream);
  916. safeClose(outputStream);
  917. } finally {
  918. tempFileManager.clear();
  919. }
  920. }
  921. @Override
  922. public void parseBody(Map<String, String> files) throws IOException, ResponseException {
  923. RandomAccessFile randomAccessFile = null;
  924. BufferedReader in = null;
  925. try {
  926. randomAccessFile = getTmpBucket();
  927. long size;
  928. if (headers.containsKey("content-length")) {
  929. size = Integer.parseInt(headers.get("content-length"));
  930. } else if (splitbyte < rlen) {
  931. size = rlen - splitbyte;
  932. } else {
  933. size = 0;
  934. }
  935. // Now read all the body and write it to f
  936. byte[] buf = new byte[512];
  937. while (rlen >= 0 && size > 0) {
  938. rlen = inputStream.read(buf, 0, (int)Math.min(size, 512));
  939. size -= rlen;
  940. if (rlen > 0) {
  941. randomAccessFile.write(buf, 0, rlen);
  942. }
  943. }
  944. // Get the raw body as a byte []
  945. ByteBuffer fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length());
  946. randomAccessFile.seek(0);
  947. // Create a BufferedReader for easily reading it as string.
  948. InputStream bin = new FileInputStream(randomAccessFile.getFD());
  949. in = new BufferedReader(new InputStreamReader(bin));
  950. // If the method is POST, there may be parameters
  951. // in data section, too, read it:
  952. if (Method.POST.equals(method)) {
  953. String contentType = "";
  954. String contentTypeHeader = headers.get("content-type");
  955. StringTokenizer st = null;
  956. if (contentTypeHeader != null) {
  957. st = new StringTokenizer(contentTypeHeader, ",; ");
  958. if (st.hasMoreTokens()) {
  959. contentType = st.nextToken();
  960. }
  961. }
  962. if ("multipart/form-data".equalsIgnoreCase(contentType)) {
  963. // Handle multipart/form-data
  964. if (!st.hasMoreTokens()) {
  965. throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html");
  966. }
  967. String boundaryStartString = "boundary=";
  968. int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.length();
  969. String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length());
  970. if (boundary.startsWith("\"") && boundary.endsWith("\"")) {
  971. boundary = boundary.substring(1, boundary.length() - 1);
  972. }
  973. decodeMultipartData(boundary, fbuf, in, parms, files);
  974. } else {
  975. // Handle application/x-www-form-urlencoded
  976. String postLine = "";
  977. StringBuilder postLineBuffer = new StringBuilder();
  978. char pbuf[] = new char[512];
  979. int read = in.read(pbuf);
  980. while (read >= 0 && !postLine.endsWith("\r\n")) {
  981. postLine = String.valueOf(pbuf, 0, read);
  982. postLineBuffer.append(postLine);
  983. read = in.read(pbuf);
  984. }
  985. postLine = postLineBuffer.toString().trim();
  986. decodeParms(postLine, parms);
  987. }
  988. } else if (Method.PUT.equals(method)) {
  989. files.put("content", saveTmpFile(fbuf, 0, fbuf.limit()));
  990. }
  991. } finally {
  992. safeClose(randomAccessFile);
  993. safeClose(in);
  994. }
  995. }
  996. /**
  997. * Decodes the sent headers and loads the data into Key/value pairs
  998. */
  999. private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, String> headers)
  1000. throws ResponseException {
  1001. try {
  1002. // Read the request line
  1003. String inLine = in.readLine();
  1004. if (inLine == null) {
  1005. return;
  1006. }
  1007. StringTokenizer st = new StringTokenizer(inLine);
  1008. if (!st.hasMoreTokens()) {
  1009. throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
  1010. }
  1011. pre.put("method", st.nextToken());
  1012. if (!st.hasMoreTokens()) {
  1013. throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
  1014. }
  1015. String uri = st.nextToken();
  1016. // Decode parameters from the URI
  1017. int qmi = uri.indexOf('?');
  1018. if (qmi >= 0) {
  1019. decodeParms(uri.substring(qmi + 1), parms);
  1020. uri = decodePercent(uri.substring(0, qmi));
  1021. } else {
  1022. uri = decodePercent(uri);
  1023. }
  1024. // If there's another token, it's protocol version,
  1025. // followed by HTTP headers. Ignore version but parse headers.
  1026. // NOTE: this now forces header names lowercase since they are
  1027. // case insensitive and vary by client.
  1028. if (st.hasMoreTokens()) {
  1029. String line = in.readLine();
  1030. while (line != null && line.trim().length() > 0) {
  1031. int p = line.indexOf(':');
  1032. if (p >= 0)
  1033. headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim());
  1034. line = in.readLine();
  1035. }
  1036. }
  1037. pre.put("uri", uri);
  1038. } catch (IOException ioe) {
  1039. throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
  1040. }
  1041. }
  1042. /**
  1043. * Decodes the Multipart Body data and put it into Key/Value pairs.
  1044. */
  1045. private void decodeMultipartData(String boundary, ByteBuffer fbuf, BufferedReader in, Map<String, String> parms,
  1046. Map<String, String> files) throws ResponseException {
  1047. try {
  1048. int[] bpositions = getBoundaryPositions(fbuf, boundary.getBytes());
  1049. int boundarycount = 1;
  1050. String mpline = in.readLine();
  1051. while (mpline != null) {
  1052. if (!mpline.contains(boundary)) {
  1053. throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html");
  1054. }
  1055. boundarycount++;
  1056. Map<String, String> item = new HashMap<String, String>();
  1057. mpline = in.readLine();
  1058. while (mpline != null && mpline.trim().length() > 0) {
  1059. int p = mpline.indexOf(':');
  1060. if (p != -1) {
  1061. item.put(mpline.substring(0, p).trim().toLowerCase(Locale.US), mpline.substring(p + 1).trim());
  1062. }
  1063. mpline = in.readLine();
  1064. }
  1065. if (mpline != null) {
  1066. String contentDisposition = item.get("content-disposition");
  1067. if (contentDisposition == null) {
  1068. throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html");
  1069. }
  1070. StringTokenizer st = new StringTokenizer(contentDisposition, ";");
  1071. Map<String, String> disposition = new HashMap<String, String>();
  1072. while (st.hasMoreTokens()) {
  1073. String token = st.nextToken().trim();
  1074. int p = token.indexOf('=');
  1075. if (p != -1) {
  1076. disposition.put(token.substring(0, p).trim().toLowerCase(Locale.US), token.substring(p + 1).trim());
  1077. }
  1078. }
  1079. String pname = disposition.get("name");
  1080. pname = pname.substring(1, pname.length() - 1);
  1081. String value = "";
  1082. if (item.get("content-type") == null) {
  1083. while (mpline != null && !mpline.contains(boundary)) {
  1084. mpline = in.readLine();
  1085. if (mpline != null) {
  1086. int d = mpline.indexOf(boundary);
  1087. if (d == -1) {
  1088. value += mpline;
  1089. } else {
  1090. value += mpline.substring(0, d - 2);
  1091. }
  1092. }
  1093. }
  1094. } else {
  1095. if (boundarycount > bpositions.length) {
  1096. throw new ResponseException(Response.Status.INTERNAL_ERROR, "Error processing request");
  1097. }
  1098. int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount - 2]);
  1099. String path = saveTmpFile(fbuf, offset, bpositions[boundarycount - 1] - offset - 4);
  1100. files.put(pname, path);
  1101. value = disposition.get("filename");
  1102. value = value.substring(1, value.length() - 1);
  1103. do {
  1104. mpline = in.readLine();
  1105. } while (mpline != null && !mpline.contains(boundary));
  1106. }
  1107. parms.put(pname, value);
  1108. }
  1109. }
  1110. } catch (IOException ioe) {
  1111. throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
  1112. }
  1113. }
  1114. /**
  1115. * Find byte index separating header from body. It must be the last byte of the first two sequential new lines.
  1116. */
  1117. private int findHeaderEnd(final byte[] buf, int rlen) {
  1118. int splitbyte = 0;
  1119. while (splitbyte + 3 < rlen) {
  1120. if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
  1121. return splitbyte + 4;
  1122. }
  1123. splitbyte++;
  1124. }
  1125. return 0;
  1126. }
  1127. /**
  1128. * Find the byte positions where multipart boundaries start.
  1129. */
  1130. private int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) {
  1131. int matchcount = 0;
  1132. int matchbyte = -1;
  1133. List<Integer> matchbytes = new ArrayList<Integer>();
  1134. for (int i = 0; i < b.limit(); i++) {
  1135. if (b.get(i) == boundary[matchcount]) {
  1136. if (matchcount == 0)
  1137. matchbyte = i;
  1138. matchcount++;
  1139. if (matchcount == boundary.length) {
  1140. matchbytes.add(matchbyte);
  1141. matchcount = 0;
  1142. matchbyte = -1;
  1143. }
  1144. } else {
  1145. i -= matchcount;
  1146. matchcount = 0;
  1147. matchbyte = -1;
  1148. }
  1149. }
  1150. int[] ret = new int[matchbytes.size()];
  1151. for (int i = 0; i < ret.le