Java tutorial
// Copyright 2009 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ut.ee.mh; //import org.apache.commons.fileupload.MultipartStream; import org.apache.commons.fileupload.MultipartStream; import org.apache.http.Header; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.RequestLine; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.DefaultHttpServerConnection; import org.apache.http.message.BasicHttpEntityEnclosingRequest; import org.apache.http.message.BasicHttpResponse; import org.apache.http.params.BasicHttpParams; import java.util.Random; import java.util.StringTokenizer; import java.util.regex.Pattern; import java.util.regex.Matcher; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.URLDecoder; import java.nio.channels.ClosedByInterruptException; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.provider.OpenableColumns; import android.util.Log; public class WebServer { private static final String TAG = "FileSharer WebServer"; private int mPort; private ServerSocketChannel mServerSocketChannel; private Context mContext; private SharedPreferences mSharedPreferences; private SQLiteDatabase mCookiesDatabase; public interface TransferStartedListener { public void started(Uri uri); } private TransferStartedListener mTransferStartedListener; /* How long we allow session cookies to last. */ private static final int COOKIE_EXPIRY_SECONDS = 3600; /* Start the webserver on specified port */ public WebServer(Context context, SharedPreferences sharedPreferences, SQLiteDatabase cookiesDatabase, int port) throws IOException { mPort = port; mServerSocketChannel = ServerSocketChannel.open(); mServerSocketChannel.socket().setReuseAddress(true); mServerSocketChannel.socket().bind(new InetSocketAddress(mPort)); mContext = context; mSharedPreferences = sharedPreferences; mCookiesDatabase = cookiesDatabase; deleteOldCookies(); } /* Returns port we're using */ public int getPort() { return mPort; } public void setOnTransferStartedListener(TransferStartedListener listener) { mTransferStartedListener = listener; } public void runWebServer() { while (true) { Log.i(TAG, "Running main webserver thread"); try { SocketChannel channel = mServerSocketChannel.accept(); final Socket socket = channel.socket(); Log.d(TAG, "Socket accepted"); Thread t = new Thread() { @Override public void run() { handleRequest(socket); } }; t.start(); } catch (ClosedByInterruptException e) { Log.i(TAG, "Received interrupt to shutdown."); return; } catch (IOException e) { Log.e(TAG, "Unexpected error, shutting down. " + e.toString()); return; } } } /* Handles a single request. */ public void handleRequest(Socket socket) { try { DefaultHttpServerConnection serverConnection = new DefaultHttpServerConnection(); serverConnection.bind(socket, new BasicHttpParams()); HttpRequest request = serverConnection.receiveRequestHeader(); RequestLine requestLine = request.getRequestLine(); BasicHttpEntityEnclosingRequest enclosingRequest = new BasicHttpEntityEnclosingRequest( request.getRequestLine()); serverConnection.receiveRequestEntity(enclosingRequest); /* First make sure user is logged in if that is required. */ boolean loggedIn = false; if (mSharedPreferences.getBoolean(FileSharingService.PREFS_REQUIRE_LOGIN, false)) { /* Does the user have a valid cookie? */ Header cookiesHeader = request.getFirstHeader("Cookie"); if (cookiesHeader != null) { String cookies = cookiesHeader.getValue(); String cookie = cookies.substring(cookies.indexOf("id=") + "id=".length()); loggedIn = isValidCookie(cookie); } } else { loggedIn = true; } // if (!loggedIn) { /* Could be the result of the login form. */ if (requestLine.getUri().equals("/getLocation")) { handlePostRequest(serverConnection, request, requestLine); } // } else if (requestLine.getUri().equals("/")) { Log.i(TAG, "Sending shared folder listing"); sendSharedFolderListing(serverConnection); } else if (requestLine.getMethod().equals("GET") && requestLine.getUri().startsWith("/folder")) { Log.i(TAG, "Sending list of shared files"); sendSharedFilesList(serverConnection, requestLine); } else if (requestLine.getUri().startsWith("/zip")) { Log.i(TAG, "Sending zip file."); sendFolderContent(serverConnection, requestLine); } else if (requestLine.getUri().startsWith("/file")) { Log.i(TAG, "Sending file content"); sendFileContent(serverConnection, requestLine); } else if (requestLine.getMethod().equals("POST")) { Log.i(TAG, "User is uploading file"); handleUploadRequest(serverConnection, request, requestLine); } else if (requestLine.getUri().startsWith("/playlist")) { Log.i(TAG, "User is requesting playlist"); sendPlaylist(serverConnection, requestLine); } else { Log.i(TAG, "No action for " + requestLine.getUri()); sendNotFound(serverConnection); } serverConnection.flush(); serverConnection.close(); } catch (IOException e) { Log.e(TAG, "Problem with socket " + e.toString()); } catch (HttpException e) { Log.e(TAG, "Problemw with HTTP server " + e.toString()); } } private void handleLocationRequest(DefaultHttpServerConnection serverConnection, HttpRequest request, RequestLine requestLine) throws HttpException, IOException { BasicHttpEntityEnclosingRequest enclosingRequest = new BasicHttpEntityEnclosingRequest( request.getRequestLine()); serverConnection.receiveRequestEntity(enclosingRequest); InputStream input = enclosingRequest.getEntity().getContent(); InputStreamReader reader = new InputStreamReader(input); StringBuffer form = new StringBuffer(); while (reader.ready()) { form.append((char) reader.read()); } String password = form.substring(form.indexOf("=") + 1); if (password.equals(mSharedPreferences.getString(FileSharingService.PREFS_PASSWORD, ""))) { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 302, "Found"); response.addHeader("Location", "/"); response.addHeader("Set-Cookie", "id=" + createCookie()); response.setEntity(new StringEntity(getHTMLHeader() + "Success!" + getHTMLFooter())); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } else { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 401, "Unauthorized"); response.setEntity( new StringEntity(getHTMLHeader() + "<p>Login failed.</p>" + getLoginForm() + getHTMLFooter())); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } } private void handleLoginRequest(DefaultHttpServerConnection serverConnection, HttpRequest request, RequestLine requestLine) throws HttpException, IOException { BasicHttpEntityEnclosingRequest enclosingRequest = new BasicHttpEntityEnclosingRequest( request.getRequestLine()); serverConnection.receiveRequestEntity(enclosingRequest); InputStream input = enclosingRequest.getEntity().getContent(); InputStreamReader reader = new InputStreamReader(input); StringBuffer form = new StringBuffer(); while (reader.ready()) { form.append((char) reader.read()); } String password = form.substring(form.indexOf("=") + 1); if (password.equals(mSharedPreferences.getString(FileSharingService.PREFS_PASSWORD, ""))) { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 302, "Found"); response.addHeader("Location", "/"); response.addHeader("Set-Cookie", "id=" + createCookie()); response.setEntity(new StringEntity(getHTMLHeader() + "Success!" + getHTMLFooter())); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } else { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 401, "Unauthorized"); response.setEntity( new StringEntity(getHTMLHeader() + "<p>Login failed.</p>" + getLoginForm() + getHTMLFooter())); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } } private String createCookie() { Random r = new Random(); String value = Long.toString(Math.abs(r.nextLong()), 36); ContentValues values = new ContentValues(); values.put("name", "id"); values.put("value", value); values.put("expiry", (int) System.currentTimeMillis() / 1000 + COOKIE_EXPIRY_SECONDS); mCookiesDatabase.insert("cookies", "name", values); return value; } private boolean isValidCookie(String cookie) { Cursor cursor = mCookiesDatabase.query("cookies", new String[] { "value" }, "name = ? and value = ? and expiry > ?", new String[] { "id", cookie, "" + (int) System.currentTimeMillis() / 1000 }, null, null, null); boolean isValid = cursor.getCount() > 0; cursor.close(); return isValid; } private void deleteOldCookies() { mCookiesDatabase.delete("cookies", "expiry < ?", new String[] { "" + (int) System.currentTimeMillis() / 1000 }); } private void sendNotFound(DefaultHttpServerConnection serverConnection) throws UnsupportedEncodingException, HttpException, IOException { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 404, "NOT FOUND"); response.setEntity(new StringEntity("NOT FOUND")); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } private void handleUploadRequest(DefaultHttpServerConnection serverConnection, HttpRequest request, RequestLine requestLine) throws IOException, HttpException, UnsupportedEncodingException { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 200, "OK"); String folderId = getFolderId(requestLine.getUri()); processUpload(folderId, request, serverConnection); String header = getHTMLHeader(); String form = getUploadForm(folderId); String footer = getHTMLFooter(); String listing = getFileListing(Uri.withAppendedPath(FileSharingProvider.Folders.CONTENT_URI, folderId)); response.setEntity(new StringEntity(header + listing + form + footer)); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } /** * Added by Carlos * * @param serverConnection * @param request * @param requestLine * @throws IOException * @throws HttpException * @throws UnsupportedEncodingException */ private void handlePostRequest(DefaultHttpServerConnection serverConnection, HttpRequest request, RequestLine requestLine) throws IOException, HttpException, UnsupportedEncodingException { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 200, "OK"); String folderId = getFolderId(requestLine.getUri()); processUpload(folderId, request, serverConnection); String header = getHTMLHeader(); String form = getUploadForm(folderId); String footer = getHTMLFooter(); String listing = getFileListing(Uri.withAppendedPath(FileSharingProvider.Folders.CONTENT_URI, folderId)); response.setEntity(new StringEntity(header + listing + form + footer)); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } private void sendFileContent(DefaultHttpServerConnection serverConnection, RequestLine requestLine) throws IOException, HttpException { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 200, "OK"); String fileId = getFileId(requestLine.getUri()); addFileEntity(Uri.withAppendedPath(FileSharingProvider.Files.CONTENT_URI, fileId), response); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } /** * Sends a ZIP file containing all files from the shared folder. * * @param serverConnection * @param requestLine * @throws IOException * @throws HttpException */ private void sendFolderContent(DefaultHttpServerConnection serverConnection, RequestLine requestLine) throws IOException, HttpException { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 200, "OK"); String folderId = getFolderId(requestLine.getUri()); addFolderZipEntity(folderId, response); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } private void addFolderZipEntity(String folderId, HttpResponse response) { response.addHeader("Content-Type", "application/zip"); response.setEntity(new StreamingZipEntity(mContext.getContentResolver(), folderId)); } private void sendPlaylist(DefaultHttpServerConnection serverConnection, RequestLine requestLine) throws IOException, HttpException { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 200, "OK"); Cursor c = mContext.getContentResolver().query(FileSharingProvider.Files.CONTENT_URI, new String[] { FileSharingProvider.Files.Columns._ID, FileSharingProvider.Files.Columns.DISPLAY_NAME }, null, null, null); String playlist = ""; while (c.moveToNext()) { long id = c.getLong(c.getColumnIndex(FileSharingProvider.Files.Columns._ID)); String name = c.getString(c.getColumnIndex(FileSharingProvider.Files.Columns.DISPLAY_NAME)); if (name.endsWith(".mp3")) { playlist += SharedFileBrowser.getShareURL(id, mContext) + "\n"; } } c.close(); response.addHeader("Content-Type", "audio/x-mpegurl"); response.addHeader("Content-Length", "" + playlist.length()); response.setEntity(new StringEntity(playlist)); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } private void sendSharedFilesList(DefaultHttpServerConnection serverConnection, RequestLine requestLine) throws UnsupportedEncodingException, HttpException, IOException { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 200, "OK"); String folderId = getFolderId(requestLine.getUri()); String header = getHTMLHeader(); String form = getUploadForm(folderId); String footer = getHTMLFooter(); String listing = getFileListing(Uri.withAppendedPath(FileSharingProvider.Folders.CONTENT_URI, folderId)); response.setEntity(new StringEntity(header + listing + form + footer)); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } private void sendLoginForm(DefaultHttpServerConnection serverConnection, RequestLine requestLine) throws UnsupportedEncodingException, HttpException, IOException { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 200, "OK"); response.setEntity( new StringEntity(getHTMLHeader() + "<p>Password Required</p>" + getLoginForm() + getHTMLFooter())); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } private void sendSharedFolderListing(DefaultHttpServerConnection serverConnection) throws UnsupportedEncodingException, HttpException, IOException { HttpResponse response = new BasicHttpResponse(new HttpVersion(1, 1), 200, "OK"); response.setEntity(new StringEntity(getHTMLHeader() + getFolderListing() + getHTMLFooter())); serverConnection.sendResponseHeader(response); serverConnection.sendResponseEntity(response); } @SuppressWarnings("deprecation") public void processUpload(String folderId, HttpRequest request, DefaultHttpServerConnection serverConnection) throws IOException, HttpException { /* Find the boundary and the content length. */ String contentType = request.getFirstHeader("Content-Type").getValue(); String boundary = contentType.substring(contentType.indexOf("boundary=") + "boundary=".length()); BasicHttpEntityEnclosingRequest enclosingRequest = new BasicHttpEntityEnclosingRequest( request.getRequestLine()); serverConnection.receiveRequestEntity(enclosingRequest); InputStream input = enclosingRequest.getEntity().getContent(); MultipartStream multipartStream = new MultipartStream(input, boundary.getBytes()); String headers = multipartStream.readHeaders(); /* Get the filename. */ StringTokenizer tokens = new StringTokenizer(headers, ";", false); String filename = null; while (tokens.hasMoreTokens() && filename == null) { String token = tokens.nextToken().trim(); if (token.startsWith("filename=")) { filename = URLDecoder.decode(token.substring("filename=\"".length(), token.lastIndexOf("\"")), "utf8"); } } File uploadDirectory = new File("/sdcard/fileshare/uploads"); if (!uploadDirectory.exists()) { uploadDirectory.mkdirs(); } /* Write the file and add it to the shared folder. */ File uploadFile = new File(uploadDirectory, filename); FileOutputStream output = new FileOutputStream(uploadFile); multipartStream.readBodyData(output); output.close(); Uri fileUri = Uri.withAppendedPath(FileProvider.CONTENT_URI, uploadFile.getAbsolutePath()); Uri folderUri = Uri.withAppendedPath(FileSharingProvider.Folders.CONTENT_URI, folderId); FileSharingProvider.addFileToFolder(mContext.getContentResolver(), fileUri, folderUri); } public String getHTMLHeader() { return "<html><head><title>File Share</title></head><body>"; } public String getHTMLFooter() { return "</body></html>"; } private String getFolderListing() { /* Get list of folders */ Cursor c = mContext.getContentResolver().query(FileSharingProvider.Folders.CONTENT_URI, null, null, null, null); int nameIndex = c.getColumnIndexOrThrow(FileSharingProvider.Folders.Columns.DISPLAY_NAME); int idIndex = c.getColumnIndexOrThrow(FileSharingProvider.Folders.Columns._ID); String s = ""; while (c.moveToNext()) { String name = c.getString(nameIndex); int id = c.getInt(idIndex); s += folderToLink(name, id) + "<br/>"; } c.close(); return s; } /** * Returns a form that allows users to upload files. */ private String getUploadForm(String folderId) { if (mSharedPreferences.getBoolean(FileSharingService.PREFS_ALLOW_UPLOADS, false)) { return "<form method=\"POST\" action=\"/folder/" + folderId + "\" " + "enctype=\"multipart/form-data\"> " + "<input type=\"file\" name=\"file\" size=\"40\"/> " + "<input type=\"submit\" value=\"Upload\"/>"; } return ""; } private String getFolderId(String firstline) { Pattern p = Pattern.compile("/(?:folder|playlist|zip)/(\\d+)"); Matcher m = p.matcher(firstline); boolean b = m.find(0); if (b) { return m.group(1); } return null; } private String getLoginForm() { return "<form method=\"POST\" action=\"/login\"" + " enctype=\"application/x-www-form-urlencoded\"" + "/><input type=\"password\" name=\"password\"/>" + "<input type=\"submit\" value=\"Login\"/></form>"; } private String getFileId(String firstline) { Pattern p = Pattern.compile("/file/(\\d+)"); Matcher m = p.matcher(firstline); boolean b = m.find(0); if (b) { return m.group(1); } return null; } private String getFileListing(Uri uri) { int folderId = Integer.parseInt(uri.getPathSegments().get(1)); Uri fileUri = FileSharingProvider.Files.CONTENT_URI; String where = FileSharingProvider.Files.Columns.FOLDER_ID + "=" + folderId; Cursor c = mContext.getContentResolver().query(fileUri, null, where, null, null); int nameIndex = c.getColumnIndexOrThrow(FileSharingProvider.Files.Columns.DISPLAY_NAME); int idIndex = c.getColumnIndexOrThrow(FileSharingProvider.Files.Columns._ID); String s = ""; boolean hasMusic = false; while (c.moveToNext()) { String name = c.getString(nameIndex); int id = c.getInt(idIndex); s += fileToLink(name, id) + "<br/>"; if (name.endsWith(".mp3")) { hasMusic = true; } } c.close(); if (hasMusic) { s += getPlaylistLink(folderId) + "<br/>"; } s += getZipLink(folderId) + "<br/>"; return s; } private String getPlaylistLink(long folderId) { return "<a href=\"/playlist/" + folderId + "/playlist.m3u\">" + "MP3 Playlist</a>"; } private String getZipLink(long folderId) { return "<a href=\"/zip/" + folderId + "/folder.zip\">" + "Zip of Entire Folder</a>"; } private void addFileEntity(final Uri uri, HttpResponse response) throws IOException { if (mTransferStartedListener != null) { mTransferStartedListener.started(uri); } Cursor c = mContext.getContentResolver().query(uri, null, null, null, null); c.moveToFirst(); int nameIndex = c.getColumnIndexOrThrow(FileSharingProvider.Files.Columns.DISPLAY_NAME); String name = c.getString(nameIndex); int dataIndex = c.getColumnIndexOrThrow(FileSharingProvider.Files.Columns._DATA); Uri data = Uri.parse(c.getString(dataIndex)); c = mContext.getContentResolver().query(data, null, null, null, null); c.moveToFirst(); int sizeIndex = c.getColumnIndexOrThrow(OpenableColumns.SIZE); int sizeBytes = c.getInt(sizeIndex); c.close(); InputStream input = mContext.getContentResolver().openInputStream(data); String contentType = "application/octet-stream"; if (name.endsWith(".jpg")) { contentType = "image/jpg"; } response.addHeader("Content-Type", contentType); response.addHeader("Content-Length", "" + sizeBytes); response.setEntity(new InputStreamEntity(input, sizeBytes)); } private String folderToLink(String folderName, int folderId) { return "<a href=\"/folder/" + folderId + "\">" + folderName + "</a>"; } private String fileToLink(String fileName, int fileId) { return "<a href=\"/file/" + fileId + "/" + fileName + "\">" + fileName + "</a>"; } }