/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html * * 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 com.google.dart.tools.debug.core.util; import com.google.common.base.Charsets; import com.google.common.io.ByteStreams; import com.google.dart.tools.core.DartCore; import com.google.dart.tools.core.DartCoreDebug; import com.google.dart.tools.core.analysis.model.IFileInfo; import com.google.dart.tools.debug.core.DartDebugCorePlugin; import com.google.dart.tools.debug.core.dartium.DartiumDebugTarget; import com.google.dart.tools.debug.core.source.UriToFileResolver; import org.apache.commons.lang3.StringEscapeUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.RandomAccessFile; import java.net.ConnectException; import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; // GET /index.html HTTP/1.1 // Host: www.example.com // HTTP/1.1 200 OK // Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux) // Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT // Content-Length: 438 // Connection: close // Content-Type: text/html; charset=UTF-8 /** * Handles an incoming http request, serving files from the workspace (or error pages) as necessary. */ class ResourceServerHandler implements Runnable { private static class HttpHeader { public static final String METHOD_GET = "GET"; public static final String METHOD_HEAD = "HEAD"; public static final String METHOD_POST = "POST"; private static final String RANGE = "Range"; public String method; public String file; public String version; public Map<String, String> headers = new LinkedHashMap<String, String>(); public int getContentLength() { String len = getHeaderKey(CONTENT_LENGTH); try { return len == null ? -1 : Integer.parseInt(len); } catch (NumberFormatException nfe) { return -1; } } /** * Return the value for the given key; assume case-insensitively for the key. * * @param key * @return */ public String getHeaderKey(String key) { String value = headers.get(key); if (value != null) { return value; } for (String k : headers.keySet()) { if (k.equalsIgnoreCase(key)) { return headers.get(k); } } return null; } public List<int[]> getRanges() { // Range: bytes=0-99,500-1499,4000- if (headers.containsKey(RANGE)) { String rangeStr = getHeaderKey(RANGE); if (rangeStr.startsWith("bytes=")) { rangeStr = rangeStr.substring("bytes=".length()); String[] strs = rangeStr.split(","); List<int[]> result = new ArrayList<int[]>(); for (String str : strs) { int index = str.indexOf('-'); try { if (index == 0) { result.add(new int[] {0, Integer.parseInt(str.substring(1))}); } else if (index == str.length() - 1) { result.add(new int[] {Integer.parseInt(str.substring(0, index)), -1}); } else if (index != -1) { result.add(new int[] { Integer.parseInt(str.substring(0, index)), Integer.parseInt(str.substring(index + 1))}); } } catch (NumberFormatException nfe) { } } return result; } } return null; } @Override public String toString() { return "[" + method + " " + file + " " + version + "]"; } void parseGetParams() { // Check for a GET request that contains getter parameters. if (file != null && file.indexOf('?') != -1) { @SuppressWarnings("unused") String params = file.substring(file.indexOf('?') + 1); file = file.substring(0, file.indexOf('?')); } } } private static class HttpResponse { public static final int OK = 200; // "OK" public static final int PARTIAL_CONTENT = 206; // "Partial Content" public static final int REDIRECT = 302; // "Found" public static final int NOT_FOUND = 404; // "Not Found" public static final int UNAUTHORIZED = 401; // "Unauthorized" public static final DateFormat RFC_1123_DATE_FORMAT = new SimpleDateFormat( "EEE, dd MMM yyyy HH:mm:ss z", Locale.US); public int responseCode = OK; public String responseText = "OK"; public Map<String, String> headers = new LinkedHashMap<String, String>(); // one or the other needs to be null public String responseBodyText; public InputStream responseBodyStream; @Override public String toString() { return "[" + responseCode + " " + responseText + "]"; } } private static final String CONTENT_TYPE = "Content-Type"; private static final String CACHE_CONTROL = "Cache-Control"; private static final String USER_AGENT = "User-Agent"; private static final String ACCEPT_RANGES = "Accept-Ranges"; private static final String CONTENT_RANGE = "Content-Range"; private static final String LAST_MODIFIED = "Last-Modified"; private static final String ISO_8859_1 = "ISO-8859-1"; private static final String US_ASCII = "US-ASCII"; public static final String CONTENT_LENGTH = "Content-Length"; private static final String TYPE_OCTET = "application/octet-stream"; private static final String TYPE_HTML = "text/html"; private static final String TYPE_PLAIN = "text/plain"; private static final String TYPE_CSS = "text/css"; private static final String TYPE_JS = "text/javascript"; private static final String TYPE_DART = "application/dart"; private static final String TYPE_XML = "text/xml"; private static final String TYPE_JPEG = "image/jpeg"; private static final String TYPE_GIF = "image/gif"; private static final String TYPE_PNG = "image/png"; private static Map<String, String> contentMappings = new HashMap<String, String>(); private static Map<String, String> extraMappings; static { contentMappings.put("htm", TYPE_HTML); contentMappings.put("html", TYPE_HTML); contentMappings.put("txt", TYPE_PLAIN); contentMappings.put("css", TYPE_CSS); contentMappings.put("js", TYPE_JS); contentMappings.put("xml", TYPE_XML); contentMappings.put("dart", TYPE_DART); contentMappings.put("jpeg", TYPE_JPEG); contentMappings.put("jpg", TYPE_JPEG); contentMappings.put("gif", TYPE_GIF); contentMappings.put("png", TYPE_PNG); setupExtraMappings(); } private static final String CRLF = "\r\n"; private static byte[] AGENT_CONTENT; /** * Special resources to serve - i.e. non-workspace resources. */ private static final String[][] embeddedResources = new String[][] { {"/favicon.ico", TYPE_GIF, "/resources/favicon.ico"}, {"/dart_16_16.gif", TYPE_GIF, "/resources/dart_16_16.gif"}, {"/dart_32_32.gif", TYPE_GIF, "/resources/dart_32_32.gif"}, {"/agent.html", TYPE_HTML, "agent.html"}, {"/agent.js", TYPE_JS, "agent.js"}, {"/apple-touch-icon-precomposed.png", TYPE_PNG, "/resources/apple-touch-icon-precomposed.png"}}; @SuppressWarnings("unused") private static byte[] getJSAgentContent() { if (AGENT_CONTENT == null) { try { AGENT_CONTENT = ByteStreams.toByteArray(ResourceServer.class.getResourceAsStream("agent.js")); } catch (IOException e) { DartDebugCorePlugin.logError(e); AGENT_CONTENT = new byte[0]; } } return AGENT_CONTENT; } private static void setupExtraMappings() { extraMappings = new HashMap<String, String>(); try { BufferedReader reader = new BufferedReader(new InputStreamReader( ResourceServerHandler.class.getResourceAsStream("mime.txt"))); String line = reader.readLine(); while (line != null) { String[] strs = line.split(" "); extraMappings.put(strs[0], strs[1]); line = reader.readLine(); } reader.close(); } catch (IOException ioe) { DartDebugCorePlugin.logError(ioe); } } private ResourceServer resourceServer; private Socket socket; public ResourceServerHandler(ResourceServer resourceServer, Socket socket) { this.resourceServer = resourceServer; this.socket = socket; } @Override public void run() { try { DataInputStream in = new DataInputStream(socket.getInputStream()); HttpHeader header = parseHeader(in); if (header == null) { if (DartDebugCorePlugin.LOGGING) { System.out.println("resource server: socket closed early"); } safeClose(socket); } else if (isAllowableConnection(socket, header)) { HttpResponse response; if (HttpHeader.METHOD_GET.equals(header.method) || HttpHeader.METHOD_HEAD.equals(header.method)) { response = createGETResponse(header); } else if (HttpHeader.METHOD_POST.equals(header.method)) { response = createPOSTResponse(header, in); } else { response = createErrorResponse("Request type " + header.method + " not supported."); } if (DartDebugCorePlugin.LOGGING) { System.out.println("resource server: " + header); if (response.responseCode != HttpResponse.OK) { System.out.println(" response: " + response); } } sendResponse(response); } else { // do nothing } socket.close(); } catch (IOException ioe) { safeClose(socket); // ignore java.net.SocketException: Connection reset // ignore java.net.SocketException: Broken pipe if (!(ioe instanceof ConnectException) && !isConnectionReset(ioe)) { DartDebugCorePlugin.logError(ioe); } } catch (Throwable t) { DartDebugCorePlugin.logError(t); } } private HttpResponse addStandardResponseHeaders(HttpResponse response) { response.headers.put("Server", "Dart Editor v" + DartCore.getVersion()); response.headers.put("Connection", "close"); return response; } /** * Restrict the files which are legal to serve. * * @param file * @return */ private boolean canServeFile(File file) { return !file.getName().startsWith("."); } private HttpResponse createErrorResponse() { return createErrorResponse(""); } private HttpResponse createErrorResponse(String message) { HttpResponse response = new HttpResponse(); response.responseCode = HttpResponse.NOT_FOUND; response.responseText = "Not Found"; response.headers.put(CONTENT_TYPE, "text/html"); response.responseBodyText = "<html><head><title>404 Not Found</title></head><body>" + StringEscapeUtils.escapeHtml4(message) + "</body></html>"; response.headers.put(CONTENT_LENGTH, Integer.toString(response.responseBodyText.length())); return addStandardResponseHeaders(response); } private HttpResponse createGETResponse(HttpHeader header) throws IOException { boolean headOnly = false; if (HttpHeader.METHOD_HEAD.equals(header.method)) { headOnly = true; } if (header.file == null || header.file.length() == 0) { return createErrorResponse("No file specified."); } if ("/".equals(header.file)) { return serveAvailableApps(header); } if (isSpecialResource(header.file)) { return serveEmbeddedResource(header.file, headOnly); } File javaFile = locateFile(header.file); // handle redirecting to mapped resources if (javaFile != null) { IResource mappedFile = locateMappedFile(javaFile); if (mappedFile != null) { return createRedirectResponse(mappedFile); } } // no longer running compilation server, instead generating js on launch // If a .dart.js file doesn't exist, check for a .dart file next to it. // if (javaFile == null && header.file.endsWith(".dart.js")) { // String dartFilePath = header.file.substring(0, header.file.length() - 3); // File dartFile = locateFile(dartFilePath); // // if (dartFile != null) { // javaFile = new File(dartFile.getPath() + ".js"); // } // } if (javaFile == null) { if (isInWorkspace(header.file)) { return createErrorResponse("File not found: " + header.file); } else { return createErrorResponse("File not found"); } } if (!canServeFile(javaFile)) { return createErrorResponse("File not found: " + header.file); } HttpResponse response = new HttpResponse(); try { // Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT Date date = new Date(javaFile.lastModified()); response.headers.put(LAST_MODIFIED, HttpResponse.RFC_1123_DATE_FORMAT.format(date)); } catch (ArrayIndexOutOfBoundsException ex) { // This happens occasionally on Windows. } catch (Throwable t) { DartDebugCorePlugin.logError(t); } // Content-Type: text/html[; charset=UTF-8] String contentType = getContentType(getFileExtension(javaFile.getName())); response.headers.put(CONTENT_TYPE, contentType); // Cache-control: no-cache response.headers.put(CACHE_CONTROL, "no-cache"); // Content-Length: 438 if (javaFile != null) { long length = javaFile.length(); // if (javaScriptContent != null) { // length = javaScriptContent.length; // } response.headers.put(CONTENT_LENGTH, Long.toString(length)); } if (!headOnly) { // if (javaScriptContent != null) { // response.responseBodyStream = new ByteArrayInputStream(javaScriptContent); // } else { List<int[]> ranges = header.getRanges(); if (ranges != null) { byte[] rangeData = readRangeData(javaFile, ranges); response.responseBodyStream = new ByteArrayInputStream(rangeData); response.responseCode = HttpResponse.PARTIAL_CONTENT; response.responseText = "Partial Content"; response.headers.put(CONTENT_LENGTH, Long.toString(rangeData.length)); // Content-Range: bytes X-Y/Z int[] range = ranges.get(0); response.headers.put(CONTENT_RANGE, "bytes " + range[0] + "-" + range[1] + "/" + rangeData.length); } else { response.responseBodyStream = new FileInputStream(javaFile); } // Indicate that we support requesting a subset of the document. response.headers.put(ACCEPT_RANGES, "bytes"); // } } addStandardResponseHeaders(response); return response; } @SuppressWarnings("unused") private HttpResponse createNotAllowedResponse() { HttpResponse response = new HttpResponse(); response.responseCode = HttpResponse.UNAUTHORIZED; response.responseText = "Unauthorized"; response.headers.put(CONTENT_TYPE, "text/html"); response.responseBodyText = "<html><head><title>401 Unauthorized</title></head><body>User agent not allowed.</body></html>"; response.headers.put(CONTENT_LENGTH, Integer.toString(response.responseBodyText.length())); addStandardResponseHeaders(response); return response; } private HttpResponse createPOSTResponse(HttpHeader header, DataInputStream in) throws IOException { // This 200000 value matches Jetty's setting. final int MAX_POST = 200000 + 100; String file = header.file; int length = header.getContentLength(); if (length > MAX_POST) { return createErrorResponse("Invalid POST length"); } byte[] data = new byte[length]; in.readFully(data); String str = new String(data); str = URLDecoder.decode(str, "UTF-8"); if ("/log".equals(file)) { handleLoggingPost(str); return addStandardResponseHeaders(new HttpResponse()); } else { // 404 NOT FOUND return createErrorResponse(); } } private HttpResponse createRedirectResponse(IResource mappedFile) { // HTTP/1.1 302 Found // Location: http://www.iana.org/domains/example/ HttpResponse response = new HttpResponse(); response.responseCode = HttpResponse.REDIRECT; response.responseText = "Found"; response.headers.put("Location", resourceServer.getUrlForResource(mappedFile)); response.headers.put(CONTENT_LENGTH, Integer.toString(0)); addStandardResponseHeaders(response); return response; } /** * @return the given string with any %20 sequences decoded */ private String decodeWebChars(String line) { // GET /dart/test%C3%BCuuuu/swipe.html HTTP/1.1 // ==> // GET /dart/testüuuuu/swipe.html HTTP/1.1 byte[] bytes = line.getBytes(); ByteArrayOutputStream out = new ByteArrayOutputStream(); for (int i = 0; i < bytes.length; i++) { if (bytes[i] == '%' && (i + 2 < bytes.length)) { int val = hex2Int((char) bytes[i + 1], (char) bytes[i + 2]); out.write(val); i += 2; } else { out.write(bytes[i]); } } return new String(out.toByteArray(), Charsets.UTF_8); } /** * Combine the given *.dart.js file and the debugger JS agent. * * @param dartJsFile * @return * @throws IOException */ // private byte[] getCombinedContentAndAgent(File dartJsFile) throws IOException { // // If we can find the source map token, then insert our debugger agent just before it. // // Otherwise, append the debugger agent to the end of the file content. // // final String SRC_MAP_TOKEN = "//@ sourceMappingURL="; // // String content = Files.toString(dartJsFile, Charsets.UTF_8); // String agent = new String(getJSAgentContent()); // // if (content.indexOf(SRC_MAP_TOKEN) != -1) { // int index = content.indexOf(SRC_MAP_TOKEN); // // content = content.substring(0, index) + agent + content.substring(index); // } else { // content += agent; // } // // return content.getBytes(Charsets.UTF_8); // } private String getContentType(String extension) { if (extension != null) { extension = extension.toLowerCase(); if (contentMappings.containsKey(extension)) { return serverHtmlAsUtf8(contentMappings.get(extension)); } if (extraMappings.containsKey(extension)) { return serverHtmlAsUtf8(extraMappings.get(extension)); } } return TYPE_OCTET; } private String getFileExtension(String name) { int index = name.lastIndexOf('.'); if (index != -1) { return name.substring(index + 1); } else { return null; } } private void handleLoggingPost(String data) throws IOException { try { JSONObject obj = new JSONObject(data); // {"response":["\"logging from sunflower\""],"cmd":"remote console.log","type":""} if (obj.has("message")) { JSONArray arr = obj.optJSONArray("message"); if (arr != null) { for (int i = 0; i < arr.length(); i++) { DartCore.getConsole().println(stripQuotes(arr.getString(i))); } } else { String log = obj.getString("message"); DartCore.getConsole().println(log); } } } catch (JSONException ex) { throw new IOException(ex); } } /** * Given two hex chars, return the resulting (byte) value. Ex. '%20' ==> 32. */ private int hex2Int(char high, char low) { StringBuffer buf = new StringBuffer(); buf.append(high); buf.append(low); try { return Integer.parseInt(buf.toString(), 16); } catch (NumberFormatException nfe) { return '%'; } } private boolean isAllowableConnection(Socket connection, HttpHeader header) { InetAddress remoteAddress = connection.getInetAddress(); if (isLocalAddress(remoteAddress)) { return true; } if (!RemoteConnectionPreferenceManager.getManager().canConnectRemote()) { return false; } // User-Agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/536.8 (KHTML, like Gecko) Chrome/20.0.1110.0 (Dart) Safari/536.8 if (DartDebugCorePlugin.getPlugin().getUserAgentManager() != null) { String userAgent = header.getHeaderKey(USER_AGENT); boolean allowed = DartDebugCorePlugin.getPlugin().getUserAgentManager().allowUserAgent( remoteAddress, userAgent); if (allowed) { resourceServer.loadingContentFrom(remoteAddress.getHostAddress(), userAgent); } return allowed; } return false; } private boolean isConnectionReset(IOException ioe) { // ignore java.net.SocketException: Connection reset // ignore java.net.SocketException: Broken pipe // ignore java.net.SocketException: Software caused connection abort: socket write error if (ioe instanceof SocketException) { String message = ioe.getMessage().toLowerCase(); if (message == null) { return false; } return message.contains("connection rese") || message.contains("connection abort") || message.contains("broken pipe"); } return false; } private boolean isInWorkspace(String filePath) { IPath path = Path.fromPortableString(filePath); if (path.segmentCount() == 0) { return false; } IProject project; try { // This can throw errors on some path inputs. project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.segment(0)); } catch (Throwable t) { return false; } if (!project.exists() || !project.isOpen()) { return false; } return true; } // private boolean isFileJsArtifact(File javaFile) { // return javaFile.getName().endsWith(".dart.js"); // } private boolean isLocalAddress(InetAddress address) { return address.isAnyLocalAddress() || address.isLoopbackAddress(); } private boolean isSpecialResource(String path) { for (String[] resourceInfo : embeddedResources) { if (resourceInfo[0].equals(path)) { return true; } } return false; } private File locateFile(String filePath) { IPath path = Path.fromPortableString(filePath); if (path.segmentCount() == 0) { return null; } IProject project; try { // This can throw errors on some path inputs. project = ResourcesPlugin.getWorkspace().getRoot().getProject(path.segment(0)); } catch (Throwable t) { return null; } if (!project.exists() || !project.isOpen()) { return null; } IPath projectLocation = project.getLocation(); if (projectLocation == null) { return null; } IPath childPath = path.removeFirstSegments(1); File file = new File(projectLocation.toFile(), childPath.toOSString()); if (DartCoreDebug.ENABLE_ANALYSIS_SERVER) { if (file.getPath().contains(DartCore.PACKAGES_DIRECTORY_PATH)) { String location = file.getPath(); String packagePath = location.substring(location.indexOf(DartCore.PACKAGES_DIRECTORY_PATH) + DartCore.PACKAGES_DIRECTORY_PATH.length()); UriToFileResolver resolver = DartiumDebugTarget.getActiveTarget().getUriToFileResolver(); String packageFileLocation = resolver.getFileForUri(DartCore.PACKAGE_SCHEME_SPEC + packagePath); if (packageFileLocation != null) { file = new File(packageFileLocation); } } } else { if (!file.exists() && childPath.toString().contains(DartCore.PACKAGES_DIRECTORY_PATH)) { int packagesIndex = childPath.toString().indexOf(DartCore.PACKAGES_DIRECTORY_PATH); String pathString = childPath.toString().substring( packagesIndex + DartCore.PACKAGES_DIRECTORY_PATH.length()); IFileInfo fileInfo = DartCore.getProjectManager().resolveUriToFileInfo( project, DartCore.PACKAGE_SCHEME_SPEC + pathString); if (fileInfo != null) { file = fileInfo.getFile(); } } } return file.exists() ? file : null; } private IResource locateMappedFile(File file) { IResource resource = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation( Path.fromOSString(file.getAbsolutePath())); if (resource instanceof IFile) { IFile resourceFile = (IFile) resource; String mappingPath = DartCore.getResourceRemapping(resourceFile); if (mappingPath != null) { IResource mappedResource = ResourcesPlugin.getWorkspace().getRoot().findMember( Path.fromPortableString(mappingPath)); if (mappedResource != null && mappedResource.exists()) { return mappedResource; } } } return null; } @SuppressWarnings("deprecation") private HttpHeader parseHeader(DataInputStream in) throws IOException { HttpHeader header = new HttpHeader(); String line = in.readLine(); if (line == null) { return null; } // GET /index.html HTTP/1.1 String[] strs = line.split(" "); if (strs.length > 2) { header.method = strs[0]; header.file = decodeWebChars(strs[1]); header.version = strs[2]; if (header.file != null) { header.file = URLDecoder.decode(header.file, ISO_8859_1); header.parseGetParams(); } } line = in.readLine(); while (line != null) { if (line.isEmpty()) { break; } int index = line.indexOf(':'); if (index == -1) { header.headers.put(line, ""); } else { header.headers.put(line.substring(0, index), line.substring(index + 1).trim()); } line = in.readLine(); } return header; } private byte[] readRangeData(File file, List<int[]> ranges) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); RandomAccessFile f = new RandomAccessFile(file, "r"); int maxLength = (int) f.length() - 1; for (int[] range : ranges) { if (range[1] > maxLength || range[1] == -1) { range[1] = maxLength; } if (range[0] >= range[1]) { continue; } f.seek(range[0]); int count = range[1] - range[0] + 1; byte[] temp = new byte[count]; f.readFully(temp); out.write(temp); } f.close(); return out.toByteArray(); } private void safeClose(Socket socket) { try { socket.close(); } catch (IOException e) { } } private void sendResponse(HttpResponse response) throws IOException { OutputStream out = socket.getOutputStream(); StringBuilder builder = new StringBuilder(); // HTTP/1.1 200 OK builder.append("HTTP/1.1 " + response.responseCode + " " + response.responseText + CRLF); for (String key : response.headers.keySet()) { builder.append(key + ": " + response.headers.get(key) + CRLF); } builder.append(CRLF); out.write(builder.toString().getBytes(ISO_8859_1)); if (response.responseBodyText != null) { out.write(response.responseBodyText.getBytes(US_ASCII)); } else if (response.responseBodyStream != null) { byte[] buffer = new byte[2048]; int count = response.responseBodyStream.read(buffer); while (count != -1) { out.write(buffer, 0, count); count = response.responseBodyStream.read(buffer); } response.responseBodyStream.close(); } out.flush(); out.close(); } private HttpResponse serveAvailableApps(HttpHeader header) throws IOException { HttpResponse response = new HttpResponse(); String content = resourceServer.getAvailableAppsContent(); byte[] bytes = content.getBytes("UTF-8"); response.headers.put(CONTENT_LENGTH, Integer.toString(bytes.length)); response.headers.put(CONTENT_TYPE, "text/html; charset=UTF-8"); response.headers.put(CACHE_CONTROL, "no-cache"); response.responseBodyStream = new ByteArrayInputStream(bytes); addStandardResponseHeaders(response); return response; } private HttpResponse serveEmbeddedResource(String path, boolean headOnly) throws IOException { for (String[] resourceInfo : embeddedResources) { if (resourceInfo[0].equals(path)) { HttpResponse response = new HttpResponse(); URL url = ResourceServerHandler.class.getResource(resourceInfo[2]); URLConnection conn = url.openConnection(); response.headers.put(CONTENT_LENGTH, Integer.toString(conn.getContentLength())); response.headers.put(CONTENT_TYPE, resourceInfo[1]); if (!headOnly) { response.responseBodyStream = conn.getInputStream(); } addStandardResponseHeaders(response); return response; } } return null; } private String serverHtmlAsUtf8(String mimeType) { if (TYPE_HTML.equals(mimeType) || TYPE_DART.equals(mimeType)) { return mimeType + "; charset=utf-8"; } else { return mimeType; } } private String stripQuotes(String str) { if (str.length() > 1 && str.startsWith("\"") && str.endsWith("\"")) { str = str.substring(1, str.length() - 1); // TODO(devoncarew): better handling of escaped values in the string ('\') str = str.replaceAll("\\\\\"", "\""); return str; } else { return str; } } }