7 package com.google.appinventor.components.runtime.util;
8 import android.os.Looper;
11 import java.util.ArrayList;
12 import java.util.Enumeration;
13 import java.util.Formatter;
14 import java.util.List;
15 import java.util.Properties;
17 import java.io.IOException;
18 import java.io.FileInputStream;
19 import java.io.FileOutputStream;
20 import java.net.InetAddress;
21 import java.net.Socket;
23 import javax.crypto.Mac;
24 import javax.crypto.spec.SecretKeySpec;
26 import android.os.Build;
27 import android.os.Handler;
28 import android.util.Log;
30 import kawa.standard.Scheme;
31 import gnu.expr.Language;
33 import android.content.Intent;
34 import android.content.pm.PackageInfo;
35 import android.content.pm.PackageManager.NameNotFoundException;
36 import android.net.Uri;
37 import org.json.JSONArray;
38 import org.json.JSONException;
39 import org.json.JSONObject;
44 private Language scheme;
46 private boolean secure;
48 private static final int YAV_SKEW_FORWARD = 1;
49 private static final int YAV_SKEW_BACKWARD = 4;
50 private static final String LOG_TAG =
"AppInvHTTPD";
51 private static byte[] hmacKey;
52 private static int seq;
53 private static final String MIME_JSON =
"application/json";
54 private final Handler androidUIHandler =
new Handler();
59 this.rootDir = wwwroot;
60 this.scheme = Scheme.getInstance(
"scheme");
63 gnu.expr.ModuleExp.mustNeverCompile();
74 public Response serve( String uri, String method, Properties header, Properties parms, Properties files, Socket mySocket )
76 Log.d(LOG_TAG, method +
" '" + uri +
"' " );
84 InetAddress myAddress = mySocket.getInetAddress();
85 String hostAddress = myAddress.getHostAddress();
86 if (!hostAddress.equals(
"127.0.0.1")) {
87 Log.d(LOG_TAG,
"Debug: hostAddress = " + hostAddress +
" while in secure mode, closing connection.");
88 Response res =
new Response(
HTTP_OK, MIME_JSON,
"{\"status\" : \"BAD\", \"message\" : \"Security Error: Invalid Source Location " + hostAddress +
"\"}");
92 res.
addHeader(
"Access-Control-Allow-Origin",
"*");
93 res.
addHeader(
"Access-Control-Allow-Headers",
"origin, content-type");
94 res.
addHeader(
"Access-Control-Allow-Methods",
"POST,OPTIONS,GET,HEAD,PUT");
95 res.
addHeader(
"Allow",
"POST,OPTIONS,GET,HEAD,PUT");
100 if (method.equals(
"OPTIONS")) {
104 Enumeration e = header.propertyNames();
105 while ( e.hasMoreElements())
107 String value = (String)e.nextElement();
108 Log.d(LOG_TAG,
" HDR: '" + value +
"' = '" +
109 header.getProperty( value ) +
"'" );
112 res.
addHeader(
"Access-Control-Allow-Origin",
"*");
113 res.
addHeader(
"Access-Control-Allow-Headers",
"origin, content-type");
114 res.
addHeader(
"Access-Control-Allow-Methods",
"POST,OPTIONS,GET,HEAD,PUT");
115 res.
addHeader(
"Allow",
"POST,OPTIONS,GET,HEAD,PUT");
120 if (uri.equals(
"/_newblocks")) {
121 adoptMainThreadClassLoader();
122 String inSeq = parms.getProperty(
"seq",
"0");
123 int iseq = Integer.parseInt(inSeq);
124 String blockid = parms.getProperty(
"blockid");
125 String code = parms.getProperty(
"code");
126 String inMac = parms.getProperty(
"mac",
"no key provided");
128 String input_code = code;
129 if (hmacKey !=
null) {
131 Mac hmacSha1 = Mac.getInstance(
"HmacSHA1");
132 SecretKeySpec key =
new SecretKeySpec(hmacKey,
"RAW");
134 byte [] tmpMac = hmacSha1.doFinal((code + inSeq + blockid).getBytes());
135 StringBuffer sb =
new StringBuffer(tmpMac.length * 2);
136 Formatter formatter =
new Formatter(sb);
137 for (
byte b : tmpMac)
138 formatter.format(
"%02x", b);
139 compMac = sb.toString();
140 }
catch (Exception e) {
141 Log.e(LOG_TAG,
"Error working with hmac", e);
147 Log.d(LOG_TAG,
"Incoming Mac = " + inMac);
148 Log.d(LOG_TAG,
"Computed Mac = " + compMac);
149 Log.d(LOG_TAG,
"Incoming seq = " + inSeq);
150 Log.d(LOG_TAG,
"Computed seq = " + seq);
151 Log.d(LOG_TAG,
"blockid = " + blockid);
152 if (!inMac.equals(compMac)) {
153 Log.e(LOG_TAG,
"Hmac does not match");
156 Response res =
new Response(
HTTP_OK, MIME_JSON,
"{\"status\" : \"BAD\", \"message\" : \"Security Error: Invalid MAC\"}");
159 if ((seq != iseq) && (seq != (iseq+1))) {
160 Log.e(LOG_TAG,
"Seq does not match");
163 Response res =
new Response(
HTTP_OK, MIME_JSON,
"{\"status\" : \"BAD\", \"message\" : \"Security Error: Invalid Seq\"}");
169 Log.e(LOG_TAG,
"Seq Fixup Invoked");
172 Log.e(LOG_TAG,
"No HMAC Key");
175 Response res =
new Response(
HTTP_OK, MIME_JSON,
"{\"status\" : \"BAD\", \"message\" : \"Security Error: No HMAC Key\"}");
179 code =
"(begin (require <com.google.youngandroid.runtime>) (process-repl-input " + blockid +
" (begin " +
182 Log.d(LOG_TAG,
"To Eval: " + code);
187 if (input_code.equals(
"#f")) {
188 Log.e(LOG_TAG,
"Skipping evaluation of #f");
193 }
catch (Throwable ex) {
194 Log.e(LOG_TAG,
"newblocks: Scheme Failure", ex);
198 res.
addHeader(
"Access-Control-Allow-Origin",
"*");
199 res.
addHeader(
"Access-Control-Allow-Headers",
"origin, content-type");
200 res.
addHeader(
"Access-Control-Allow-Methods",
"POST,OPTIONS,GET,HEAD,PUT");
201 res.
addHeader(
"Allow",
"POST,OPTIONS,GET,HEAD,PUT");
203 }
else if (uri.equals(
"/_values")) {
205 res.
addHeader(
"Access-Control-Allow-Origin",
"*");
206 res.
addHeader(
"Access-Control-Allow-Headers",
"origin, content-type");
207 res.
addHeader(
"Access-Control-Allow-Methods",
"POST,OPTIONS,GET,HEAD,PUT");
208 res.
addHeader(
"Allow",
"POST,OPTIONS,GET,HEAD,PUT");
210 }
else if (uri.equals(
"/_getversion")) {
213 String packageName = form.getPackageName();
214 PackageInfo pInfo = form.getPackageManager().getPackageInfo(packageName, 0);
219 installer =
"Not Known";
224 String versionName = pInfo.versionName;
225 if (installer ==
null)
226 installer =
"Not Known";
230 res =
new Response(
HTTP_OK, MIME_JSON,
"{\"version\" : \"" + versionName +
231 "\", \"fingerprint\" : \"" + Build.FINGERPRINT +
"\"," +
232 " \"installer\" : \"" + installer +
"\", \"package\" : \"" +
233 packageName +
"\", \"fqcn\" : true }");
234 }
catch (NameNotFoundException n) {
238 res.
addHeader(
"Access-Control-Allow-Origin",
"*");
239 res.
addHeader(
"Access-Control-Allow-Headers",
"origin, content-type");
240 res.
addHeader(
"Access-Control-Allow-Methods",
"POST,OPTIONS,GET,HEAD,PUT");
241 res.
addHeader(
"Allow",
"POST,OPTIONS,GET,HEAD,PUT");
244 androidUIHandler.post(
new Runnable() {
251 }
else if (uri.equals(
"/_extensions")) {
252 return processLoadExtensionsRequest(parms);
255 if (method.equals(
"PUT")) {
256 Boolean error =
false;
257 String tmpFileName = (String) files.getProperty(
"content",
null);
258 if (tmpFileName !=
null) {
259 File fileFrom =
new File(tmpFileName);
260 String filename = parms.getProperty(
"filename",
null);
261 if (filename !=
null) {
262 if (filename.startsWith(
"..") || filename.endsWith(
"..")
263 || filename.indexOf(
"../") >= 0) {
264 Log.d(LOG_TAG,
" Ignoring invalid filename: " + filename);
268 if (filename !=
null) {
270 File fileTo =
new File(rootDir +
"/" + filename);
271 File parentFileTo = fileTo.getParentFile();
272 if (!parentFileTo.exists()) {
273 parentFileTo.mkdirs();
275 if (!fileFrom.renameTo(fileTo)) {
276 error = copyFile(fileFrom, fileTo);
281 Log.e(LOG_TAG,
"Received content without a file name!");
285 Log.e(LOG_TAG,
"Received PUT without content.");
290 res.
addHeader(
"Access-Control-Allow-Origin",
"*");
291 res.
addHeader(
"Access-Control-Allow-Headers",
"origin, content-type");
292 res.
addHeader(
"Access-Control-Allow-Methods",
"POST,OPTIONS,GET,HEAD,PUT");
293 res.
addHeader(
"Allow",
"POST,OPTIONS,GET,HEAD,PUT");
297 res.
addHeader(
"Access-Control-Allow-Origin",
"*");
298 res.
addHeader(
"Access-Control-Allow-Headers",
"origin, content-type");
299 res.
addHeader(
"Access-Control-Allow-Methods",
"POST,OPTIONS,GET,HEAD,PUT");
300 res.
addHeader(
"Allow",
"POST,OPTIONS,GET,HEAD,PUT");
305 return serveFile( uri, header, rootDir,
true );
308 private boolean copyFile(File infile, File outfile) {
310 FileInputStream in =
new FileInputStream(infile);
311 FileOutputStream out =
new FileOutputStream(outfile);
312 byte[] buffer =
new byte[32768];
315 while ((len = in.read(buffer)) > 0) {
316 out.write(buffer, 0, len);
322 }
catch (IOException e) {
328 private Response processLoadExtensionsRequest(Properties parms) {
330 JSONArray array =
new JSONArray(parms.getProperty(
"extensions",
"[]"));
331 List<String> extensionsToLoad =
new ArrayList<String>();
332 for (
int i = 0; i < array.length(); i++) {
333 String extensionName = array.optString(i);
334 if (extensionName !=
null) {
335 extensionsToLoad.add(extensionName);
337 return error(
"Invalid JSON content at index " + i);
342 }
catch (Exception e) {
345 return message(
"OK");
346 }
catch (JSONException e) {
358 private void adoptMainThreadClassLoader() {
359 ClassLoader mainClassLoader = Looper.getMainLooper().getThread().getContextClassLoader();
360 Thread myThread = Thread.currentThread();
361 if (myThread.getContextClassLoader() != mainClassLoader) {
362 myThread.setContextClassLoader(mainClassLoader);
366 private Response message(String txt) {
370 private Response json(String json) {
371 return addHeaders(
new Response(
HTTP_OK, MIME_JSON, json));
374 private Response error(String msg) {
375 JSONObject result =
new JSONObject();
377 result.put(
"status",
"BAD");
378 result.put(
"message", msg);
379 }
catch(JSONException e) {
380 Log.wtf(LOG_TAG,
"Unable to write basic JSON content", e);
382 return addHeaders(
new Response(
HTTP_OK, MIME_JSON, result.toString()));
385 private Response error(Throwable t) {
386 return error(t.toString());
389 private Response addHeaders(Response res) {
390 res.
addHeader(
"Access-Control-Allow-Origin",
"*");
391 res.addHeader(
"Access-Control-Allow-Headers",
"origin, content-type");
392 res.addHeader(
"Access-Control-Allow-Methods",
"POST,OPTIONS,GET,HEAD,PUT");
393 res.addHeader(
"Allow",
"POST,OPTIONS,GET,HEAD,PUT");
402 hmacKey = inputKey.getBytes();