PageRenderTime 81ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 1ms

/php/app.php

https://github.com/KobeCena/jsbin
PHP | 988 lines | 834 code | 100 blank | 54 comment | 219 complexity | 106cf753c6f44b18e3ca1cbc86632389 MD5 | raw file
Possible License(s): MIT, BSD-3-Clause
  1. <?php
  2. date_default_timezone_set('Europe/London');
  3. require_once('../vendor/bcrypt.php');
  4. require_once('../vendor/mustache.php');
  5. include('config.php'); // contains DB & important versioning
  6. include('blacklist.php'); // rules to *try* to prevent abuse of jsbin
  7. $embed = false;
  8. $host = 'http://' . $_SERVER['HTTP_HOST'];
  9. // allows for custom hosting of jsbin - special feature for teachers
  10. $cname = '';
  11. $custom = array();
  12. // a global - sorry folks / @ac94!
  13. $last_updated = '';
  14. preg_match('/^([a-z0-9\-]+)\.' . HOST . '$/', $_SERVER['HTTP_HOST'], $match);
  15. if (count($match) == 2) {
  16. $cname = $match[1];
  17. }
  18. $custom = array();
  19. if ($cname && $cname !== 'www') { // unlikely on the www
  20. // we have a custom build of jsbin - let's load their customisations
  21. if (file_exists('custom/' . $cname . '/config.json')) {
  22. $custom = json_decode(file_get_contents('custom/' . $cname . '/config.json'), true);
  23. $custom['__dirname'] = 'custom/' . $cname . '/';
  24. }
  25. }
  26. $pos = strpos($_SERVER['REQUEST_URI'], PATH);
  27. if ($pos !== false) $pos = strlen(PATH);
  28. $request_uri = substr($_SERVER['REQUEST_URI'], $pos);
  29. $session = isset($_COOKIE['session']) ? $_COOKIE['session'] : null;
  30. if ($session) {
  31. $hash = substr($session, 0, 40);
  32. $json = substr($session, 40);
  33. if ($hash = session_hash($json)) {
  34. $session = json_decode($json, true);
  35. } else {
  36. $session = array();
  37. }
  38. }
  39. $home = isset($session['user']) ? $session['user']['name'] : '';
  40. $email = isset($session['user']) ? $session['user']['email'] : '';
  41. $csrf = isset($_COOKIE['_csrf']) ? $_COOKIE['_csrf'] : md5(rand());
  42. if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD'))) {
  43. if (!(
  44. (isset($_GET['_csrf']) && $_GET['_csrf'] === $csrf) ||
  45. (isset($_POST['_csrf']) && $_POST['_csrf'] === $csrf) ||
  46. (isset($_SERVER['HTTP_X_CSRF_TOKEN']) && $_SERVER['HTTP_X_CSRF_TOKEN'] === $csrf)
  47. )) {
  48. header("HTTP/1.1 403 Access Forbidden");
  49. header("Content-Type: text/plain");
  50. echo 'Request failed CSRF check';
  51. exit;
  52. }
  53. }
  54. setcookie('_csrf', $csrf, 0, PATH);
  55. // if ($request_uri == '' && $home && stripos($_SERVER['HTTP_HOST'], $home . '/') !== 0) {
  56. // header('Location: ' . HOST . $home . '/');
  57. // exit;
  58. // }
  59. $request = explode('/', preg_replace('/^\//', '', preg_replace('/\/$/', '', preg_replace('/\?.*$/', '', $request_uri ))));
  60. $action = array_pop($request);
  61. if (stripos($action, '.') !== false) {
  62. $parts = explode('\.', $action);
  63. array_push($request, $parts[0]);
  64. $action = $parts[1];
  65. }
  66. // remove the home path section from the request so we can correctly read the next action
  67. if ($action == $home) {
  68. $action = array_pop($request);
  69. }
  70. // allow us to request .html
  71. if ($action == 'html') {
  72. $action = array_pop($request);
  73. }
  74. $quiet = false;
  75. if ($action == 'quiet') {
  76. $quiet = true;
  77. $action = array_pop($request);
  78. }
  79. // allows the user to save over existing jsbins
  80. $edit_mode = true; // determines whether we should go ahead and load index.php
  81. $code_id = '';
  82. // if it contains the x-requested-with header, or is a CORS request on GET only
  83. $ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) || (stripos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false && $_SERVER['REQUEST_METHOD'] == 'GET');
  84. $no_code_found = false;
  85. // respond to preflights
  86. if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
  87. // return only the headers and not the content
  88. // only allow CORS if we're doing a GET - i.e. no saving for now.
  89. if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'GET') {
  90. header('Access-Control-Allow-Origin: *');
  91. header('Access-Control-Allow-Headers: X-Requested-With');
  92. }
  93. exit;
  94. } else if ($ajax) {
  95. header('Access-Control-Allow-Origin: *');
  96. }
  97. // doesn't require a connection when we're landing for the first time
  98. // if ($action) {
  99. connect();
  100. // }
  101. if (!$action) {
  102. // do nothing and serve up the page
  103. } else if ($action == 'logout' && $_SERVER['REQUEST_METHOD'] == 'POST') {
  104. $referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
  105. $referrer = !$referrer && isset($_SERVER['HTTP_REFERRER']) ? $_SERVER['HTTP_REFERRER'] : HOME;
  106. $redirect = isset($_POST['_redirect']) ? $_POST['_redirect'] : $referrer;
  107. if (!$redirect || stripos($redirect, '://') !== false) {
  108. $redirect = ROOT;
  109. }
  110. header('HTTP/1.1 303 Found');
  111. setcookie('session', null, -1, PATH);
  112. header('Location: ' . $redirect);
  113. exit;
  114. } else if ($action == 'sethome') {
  115. if ($ajax) {
  116. // Logic is in account-logic.md
  117. $bcrypt = new Bcrypt(10);
  118. $key = $_POST['key'];
  119. $name = $_POST['name'];
  120. $email = $_POST['email'];
  121. $saved_email = $email; // this gets overwritten later
  122. // TODO allow email to be used as the lookup key
  123. $sql = sprintf('select * from ownership where name="%s"', mysql_real_escape_string($name));
  124. $result = mysql_query($sql);
  125. $ok = false;
  126. $created = false;
  127. $rows_found = mysql_num_rows($result);
  128. header('content-type: application/json');
  129. // (3) if no rows found, and there's an email AND they're not logged in already!
  130. if ($rows_found == 0 && strlen($email) && !$home) {
  131. // store and okay (note "key" is a reserved word - typical!)
  132. $key = $bcrypt->hash($key);
  133. $sql = sprintf('insert into ownership (`name`, `key`, `email`, `last_login`, `created`, `updated`) values ("%s", "%s", "%s", NOW(), NOW(), NOW())', mysql_real_escape_string($name), mysql_real_escape_string($key), mysql_real_escape_string($email));
  134. $result = mysql_query($sql);
  135. // (4) created account
  136. if ($result) {
  137. $ok = true;
  138. $created = true;
  139. // echo json_encode(array('ok' => true, 'created' => true));
  140. } else {
  141. echo json_encode(array('ok' => false, 'message' => 'Something went wrong when I tried to create your account :-\\', 'error' => mysql_error(), 'step' => '3'));
  142. }
  143. // (1) ERROR - user name doesn't exist
  144. } else if (!$email && !$home && !$rows_found) {
  145. // log in attempt when username wasn't found
  146. echo json_encode(array('ok' => false, 'message' => "No dice I'm afraid, those details didn't work.", 'step' => '1'));
  147. // (2) user was found with that name
  148. } else if ($rows_found) {
  149. // check key
  150. $row = mysql_fetch_object($result);
  151. $saved_email = $row->email;
  152. $hashed = $row->key;
  153. $saved_name = $row->name;
  154. $created = date_parse($row->created);
  155. // (2.1) account hasn't been updated to bcrypt
  156. if (!$created || $created['warning_count']) {
  157. // (2.1.1) sha1 matches
  158. if ($hashed === sha1($key)) {
  159. $hashed = $bcrypt->hash($key);
  160. $sql = sprintf('UPDATE ownership SET `key`="%s", `last_login`=NOW(), `created`=NOW(), `updated`=NOW() WHERE `name`="%s"', mysql_real_escape_string($hashed), mysql_real_escape_string($name));
  161. if (!mysql_query($sql)) {
  162. echo json_encode(array('ok' => false, 'error' => mysql_error(), 'step' => '2.1.1'));
  163. exit;
  164. }
  165. }
  166. }
  167. if ($home) {
  168. $ok = true;
  169. if ($email) {
  170. $saved_email = $email;
  171. $sql = sprintf('UPDATE ownership SET `email`="%s" WHERE `name`="%s"', mysql_real_escape_string($email), mysql_real_escape_string($name));
  172. if (!mysql_query($sql)) {
  173. echo json_encode(array('ok' => false, 'error' => mysql_error(), 'step' => '5.1'));
  174. exit;
  175. }
  176. }
  177. if ($key) {
  178. $saved_email = $email;
  179. $sql = sprintf('UPDATE ownership SET `key`="%s" WHERE `name`="%s"', mysql_real_escape_string($bcrypt->hash($key)), mysql_real_escape_string($name));
  180. if (!mysql_query($sql)) {
  181. echo json_encode(array('ok' => false, 'error' => mysql_error(), 'step' => '5.2'));
  182. exit;
  183. }
  184. }
  185. // (2.2) check bcrypt passsowrd matches
  186. } else if ($bcrypt->verify($key, $hashed)) {
  187. // otherwise username & password were okay, update their details (including email addy)
  188. $ok = true;
  189. $sql = sprintf('UPDATE ownership SET `last_login`=NOW() WHERE `name`="%s"', mysql_real_escape_string($name));
  190. // (2.2.1) logged in, also update their email address
  191. if ($email && $home) {
  192. $sql = sprintf('UPDATE ownership SET `email`="%s", `last_login`=NOW() WHERE `name`="%s"', mysql_real_escape_string($email), mysql_real_escape_string($name));
  193. $saved_email = $email;
  194. }
  195. if (!mysql_query($sql)) {
  196. echo json_encode(array('ok' => false, 'error' => mysql_error(), 'step' => '2.2.1'));
  197. exit;
  198. }
  199. } else {
  200. // (2.3) found username, but the password didn't match
  201. if ($email && !$home) {
  202. echo json_encode(array('ok' => false, 'message' => "Too late I'm afraid, that username is taken.", 'step' => '2.3'));
  203. // (2.4) password doesn't match
  204. } else {
  205. echo json_encode(array('ok' => false, 'message' => "No dice I'm afraid, those details didn't work.", 'step' => '2.4'));
  206. }
  207. }
  208. } else {
  209. // (6) trying to change username - we don't support this yet
  210. if ($home && $home != $saved_name && !$rows_found) {
  211. // trying to change their username - not supported yet.
  212. echo json_encode(array('ok' => false, 'message' => "Sorry, changing your username isn't supported just yet. We're on it though!", 'step' => '6'));
  213. } else {
  214. echo json_encode(array('ok' => false, 'message' => "Ooh, this is embarrassing. Something's failed and I'm not quite sure what..."));
  215. }
  216. }
  217. if ($ok) {
  218. setSession($name, $saved_email);
  219. if ($home) {
  220. echo json_encode(array('ok' => true, 'message' => 'Account updated.'));
  221. } else {
  222. echo json_encode(array('ok' => true, 'created' => $created));
  223. }
  224. }
  225. exit;
  226. }
  227. // disabling - not sure this looks right
  228. } else if (FALSE && ($action == 'updatehome' && $_SERVER['REQUEST_METHOD'] == 'POST')) {
  229. $key = isset($_POST['key']) ? trim($_POST['key']) : null;
  230. $email = isset($_POST['email']) ? trim($_POST['email']) : null;
  231. $set = array();
  232. if (!$home) {
  233. exit;
  234. }
  235. if ($email) {
  236. array_push($set, '`email`="' . mysql_real_escape_string($email) . '"');
  237. }
  238. if ($key) {
  239. $bcrypt = new Bcrypt(10);
  240. $hashed = $bcrypt->hash($key);
  241. array_push($set, '`key`="' . mysql_real_escape_string($hashed) . '"');
  242. }
  243. if (!mysql_query(sprintf('UPDATE ownership SET %s WHERE `name`="%s"', implode($set, ', '), mysql_real_escape_string($home)))) {
  244. header("HTTP/1.1 500 Internal Server Error");
  245. echo json_encode(array('ok' => false, 'error' => mysql_error()));
  246. exit;
  247. }
  248. if ($ajax) {
  249. echo json_encode(array('ok' => true, 'error' => false));
  250. } else {
  251. header('Location: ' . PATH);
  252. }
  253. exit;
  254. } else if ($action == 'forgot') {
  255. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  256. $email = isset($_POST['email']) ? trim($_POST['email']) : null;
  257. if (!$email) {
  258. echo json_encode(array('error' => 'Please provide a valid email address'));
  259. exit;
  260. } else {
  261. $sql = 'SELECT * FROM `ownership` WHERE `email`="%s" LIMIT 1';
  262. $sql = sprintf($sql, mysql_real_escape_string($email));
  263. $result = mysql_query($sql);
  264. if (!mysql_num_rows($result)) {
  265. echo json_encode(array('error' => 'Unable to find a user for that email'));
  266. header("HTTP/1.1 404 Not Found");
  267. exit;
  268. }
  269. $user = mysql_fetch_object($result);
  270. $token = md5(rand());
  271. $expires = date('Y-m-d H:i:s', time() + (24 * 60 * 60));
  272. $sql = 'INSERT INTO `forgot_tokens` (`owner_name`, `token`, `expires`, `created`) VALUES ("%s", "%s", "%s", NOW())';
  273. $sql = sprintf($sql, mysql_real_escape_string($user->name), $token, $expires);
  274. if (!mysql_query($sql)) {
  275. header("HTTP/1.1 500 Internal Server Error");
  276. echo json_encode(array('ok' => false, 'error' => mysql_error()));
  277. exit;
  278. }
  279. $view = file_get_contents('../views/reset_email.txt');
  280. $mustache = new Mustache;
  281. $mail_body = $mustache->render($view, array(
  282. 'link' => ROOT . '/reset?token=' . $token,
  283. 'domain' => $_SERVER['SERVER_NAME']
  284. ));
  285. $result = mail($user->email, 'JSBin Password Reset', $mail_body, 'From: JSBin <dave-the-robot@jsbin.com>');
  286. if (!$result) {
  287. header("HTTP/1.1 500 Internal Server Error");
  288. echo json_encode(array('ok' => false, 'error' => 'Unable to send email'));
  289. exit;
  290. }
  291. if ($ajax) {
  292. echo json_encode(array());
  293. } else {
  294. header('Location: ' . PATH);
  295. }
  296. }
  297. } else {
  298. $view = file_get_contents('../views/request.html');
  299. $mustache = new Mustache;
  300. echo $mustache->render($view, array(
  301. 'csrf' => $csrf,
  302. 'action' => ROOT . '/forgot'
  303. ));
  304. }
  305. exit;
  306. } else if ($action == 'reset') {
  307. $token = isset($_GET['token']) ? trim($_GET['token']) : null;
  308. $user = null;
  309. if (!$token) {
  310. header('Location: ' . PATH);
  311. exit;
  312. }
  313. $sql = 'SELECT `ownership`.*, expires FROM `ownership` INNER JOIN `forgot_tokens` ON `name` = `owner_name` WHERE `token` = "%s" AND `forgot_tokens`.`expires` >= NOW()';
  314. $sql = sprintf($sql, mysql_real_escape_string($token));
  315. $result = mysql_query($sql);
  316. if (!mysql_num_rows($result)) {
  317. header('Location: ' . PATH);
  318. exit;
  319. } else {
  320. $user = mysql_fetch_object($result);
  321. }
  322. $sql = 'DELETE FROM `forgot_tokens` WHERE `expires` <= NOW() OR `owner_name`="%s"';
  323. $sql = sprintf($sql, mysql_real_escape_string($user->name));
  324. mysql_query($sql);
  325. if ($user) {
  326. setSession($user->name, $user->email);
  327. $view = file_get_contents('../views/account.html');
  328. $mustache = new Mustache;
  329. echo $mustache->render($view, array(
  330. 'email' => $user->email,
  331. 'csrf' => $csrf,
  332. 'action' => ROOT . '/updatehome'
  333. ));
  334. } else {
  335. header('Location: ' . PATH);
  336. }
  337. exit;
  338. } else if ($action == 'list' || $action == 'show') {
  339. showSaved($request[0] ? $request[0] : $home);
  340. // could be listed under a user OR could be listing all the revisions for a particular bin
  341. exit();
  342. } else if ($action == 'source' || $action == 'js' || $action == 'css' || $action == 'json') {
  343. list($code_id, $revision) = getCodeIdParams($request);
  344. $edit_mode = false;
  345. if ($code_id) {
  346. list($latest_revision, $html, $javascript, $css) = getCode($code_id, $revision);
  347. } else {
  348. list($latest_revision, $html, $javascript, $css) = defaultCode();
  349. }
  350. if ($action == 'js') {
  351. header('Content-type: text/javascript');
  352. echo $javascript;
  353. } else if ($action == 'json') {
  354. header('Content-type: application/json');
  355. echo $javascript;
  356. } else if ($action == 'css') {
  357. header('Content-type: text/css');
  358. echo $css;
  359. } else {
  360. header('Content-type: application/json');
  361. $url = ROOT . $code_id . ($revision == 1 ? '' : '/' . $revision);
  362. if (!$ajax) {
  363. echo 'var template = ';
  364. }
  365. // doubles as JSON
  366. echo '{"url":"' . $url . '","html" : ' . encode($html) . ',"css":' . encode($css) . ',"javascript":' . encode($javascript) . '}';
  367. }
  368. } else if ($action == 'edit' || $action == 'embed') {
  369. list($code_id, $revision) = getCodeIdParams($request);
  370. if ($action == 'embed') $embed = true;
  371. if ($revision == 'latest') {
  372. $latest_revision = getMaxRevision($code_id);
  373. header('Location: /' . $code_id . '/' . $latest_revision . '/edit');
  374. $edit_mode = false;
  375. }
  376. } else if ($action == 'save' || $action == 'clone') {
  377. list($code_id, $revision) = getCodeIdParams($request);
  378. $javascript = @$_POST['javascript'];
  379. $html = @$_POST['html'];
  380. $css = @$_POST['css'];
  381. $method = @$_POST['method'];
  382. $stream = isset($_POST['stream']) ? true : false;
  383. $streaming_key = '';
  384. $allowUpdate = false;
  385. // we're using stripos instead of == 'save' because the method *can* be "download,save" to support doing both
  386. if (stripos($method, 'save') !== false) {
  387. if (stripos($method, 'new') !== false) {
  388. $code_id = false;
  389. }
  390. if (!$code_id) {
  391. $code_id = generateCodeId();
  392. $revision = 1;
  393. } else {
  394. $revision = getMaxRevision($code_id);
  395. $revision++;
  396. }
  397. $rewriteKey = md5(strval($code_id) . strval($revision) . strval(mt_rand()));
  398. $sql = sprintf('insert into sandbox (javascript, css, html, created, last_viewed, url, revision, streaming_key) values ("%s", "%s", "%s", now(), now(), "%s", "%s", "%s")', mysql_real_escape_string($javascript), mysql_real_escape_string($css), mysql_real_escape_string($html), mysql_real_escape_string($code_id), mysql_real_escape_string($revision), mysql_real_escape_string($rewriteKey));
  399. // a few simple tests to pass before we save
  400. if (($html == '' && $html == $javascript && $css == '')) {
  401. // entirely blank isn't going to be saved.
  402. } else {
  403. if (!noinsert($html, $javascript)) {
  404. $ok = mysql_query($sql);
  405. if ($ok) {
  406. $allowUpdate = $rewriteKey;
  407. }
  408. if ($home && $ok) {
  409. $sql = sprintf('insert into owners (name, url, revision) values ("%s", "%s", "%s")', mysql_real_escape_string($home), mysql_real_escape_string($code_id), mysql_real_escape_string($revision));
  410. $ok = mysql_query($sql);
  411. }
  412. }
  413. }
  414. } else if ($method === 'update') {
  415. $checksum = $_POST['checksum'];
  416. $code_id = $_POST['code'];
  417. $revision = $_POST['revision'];
  418. $panel = $_POST['panel'];
  419. $content = $_POST['content'];
  420. if ($panel === 'javascript' || $panel === 'css' || $panel === 'html') {
  421. $sql = sprintf('update sandbox set %s="%s", created=now() where url="%s" and revision="%s" and streaming_key="%s" and streaming_key!=""', mysql_real_escape_string($panel), mysql_real_escape_string($content), mysql_real_escape_string($code_id), mysql_real_escape_string($revision), mysql_real_escape_string($checksum));
  422. // TODO run against blacklist
  423. $ok = mysql_query($sql);
  424. $updated = mysql_affected_rows();
  425. if ($ok && $updated === 1) {
  426. $data = array(ok => true, error => false);
  427. echo json_encode($data);
  428. exit;
  429. } else {
  430. $data = array(
  431. error => true,
  432. message => 'checksum did not check out on revision update'
  433. );
  434. echo json_encode($data);
  435. exit;
  436. }
  437. } else {
  438. $data = array(
  439. error => true,
  440. message => 'oi oi, you naughty boy'
  441. );
  442. echo json_encode($data);
  443. exit;
  444. }
  445. }
  446. // If they're saving via an XHR request, then second back JSON or JSONP response
  447. if ($ajax) {
  448. // supports plugins making use of JS Bin via ajax calls and callbacks
  449. if (array_key_exists('callback', $_REQUEST)) {
  450. echo $_REQUEST['callback'] . '("';
  451. }
  452. $url = ROOT . '/' . $code_id . '/' . $revision;
  453. if (isset($_REQUEST['format']) && strtolower($_REQUEST['format']) == 'plain') {
  454. echo $url;
  455. } else {
  456. // FIXME - why *am* I sending "js" and "html" with the url to the bin?
  457. $data = array(
  458. code => $code_id,
  459. root => PATH,
  460. created => date('c', time()),
  461. revision => $revision,
  462. url => $url,
  463. edit => $url . '/edit',
  464. html => $url . '/edit',
  465. js => $url . '/edit',
  466. title => getTitleFromCode(array(html => $html, javascript => $javascript)),
  467. allowUpdate => $allowUpdate === false ? false : true,
  468. checksum => $allowUpdate
  469. );
  470. echo json_encode($data);
  471. // echo '{ "code": "' . $code_id . '", "root": "' . PATH . '", "created": "' . date('c', time()) . '", "revision": ' . $revision . ', "url" : "' . $url . '", "edit" : "' . $url . '/edit", "html" : "' . $url . '/edit", "js" : "' . $url . '/edit", "title": "' . .'" }';
  472. }
  473. if (array_key_exists('callback', $_REQUEST)) {
  474. echo '")';
  475. }
  476. } else {
  477. // code was saved, so lets do a location redirect to the newly saved code
  478. $edit_mode = false;
  479. if ($revision == 1) {
  480. header('Location: ' . ROOT . $code_id . '/edit');
  481. } else {
  482. header('Location: ' . ROOT . $code_id . '/' . $revision . '/edit');
  483. }
  484. }
  485. } else if ($action === 'download' || $action) { // this should be an id
  486. $subaction = array_pop($request);
  487. if ($action == 'latest') {
  488. // find the latest revision and redirect to that.
  489. $code_id = $subaction;
  490. $latest_revision = getMaxRevision($code_id);
  491. header('Location: /' . $code_id . '/' . $latest_revision);
  492. $edit_mode = false;
  493. }
  494. // gist are formed as jsbin.com/gist/1234 - which land on this condition, so we need to jump out, just in case
  495. else if ($subaction != 'gist') {
  496. $download = false;
  497. if ($action === 'download') {
  498. $download = true;
  499. $action = $subaction;
  500. $subaction = array_pop($request);
  501. }
  502. if ($subaction && is_numeric($action)) {
  503. $code_id = $subaction;
  504. $revision = $action;
  505. } else {
  506. $code_id = $action;
  507. $revision = getMaxRevision($code_id);
  508. }
  509. list($latest_revision, $html, $javascript, $css) = getCode($code_id, $revision);
  510. list($html, $javascript, $css) = formatCompletedCode($html, $javascript, $css, $code_id, $revision);
  511. global $quiet;
  512. if ($download) {
  513. $ext = $html ? 'html' : 'js';
  514. $filename = implode(array('jsbin', $code_id, $revision, $ext), '.');
  515. header('Content-Disposition: attachment; filename="' . $filename . '"');
  516. }
  517. // using new str_lreplace to ensure only the *last* </body> is replaced.
  518. // FIXME there's still a bug here if </body> appears in the script and not in the
  519. // markup - but I'll fix that later
  520. if (!$quiet && !$download) {
  521. $html = str_lreplace('</body>', '<script src="' . ROOT . '/js/render/edit.js"></script>' . "\n</body>", $html);
  522. }
  523. if ($no_code_found == false && !$download) {
  524. $html = str_lreplace('</body>', googleAnalytics() . '</body>', $html);
  525. }
  526. if (false) {
  527. if (stripos($html, '<head>')) {
  528. $html = preg_replace('/<head>(.*)/', '<head><script>if (window.top != window.self) window.top.location.replace(window.location.href);</script>$1', $html);
  529. } else {
  530. // if we can't find a head element, brute force the framebusting in to the HTML
  531. $html = '<script>if (window.top != window.self) window.top.location.replace(window.location.href);</script>' . $html;
  532. }
  533. }
  534. if (!$html && !$ajax) {
  535. $javascript = "/*\n Created using " . ROOT . "\n Source can be edit via " . $host . ROOT . "$code_id/edit\n*/\n\n" . $javascript;
  536. }
  537. if (!$html) {
  538. header("Content-type: text/javascript");
  539. }
  540. if ($last_updated) {
  541. header("Last-Modified: " . date('r', strtotime($last_updated)));
  542. }
  543. echo $html ? $html : $javascript;
  544. $edit_mode = false;
  545. }
  546. }
  547. if (!$edit_mode || $ajax) {
  548. exit;
  549. }
  550. function connect() {
  551. // sniff, and if on my mac...
  552. $link = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD);
  553. mysql_select_db(DB_NAME, $link);
  554. }
  555. function encode($s) {
  556. static $jsonReplaces = array(array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"'), array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"'));
  557. return '"' . str_replace($jsonReplaces[0], $jsonReplaces[1], $s) . '"';
  558. }
  559. function str_lreplace($search, $replace, $subject) {
  560. $pos = strrpos($subject, $search);
  561. if ($pos === false) {
  562. return $subject;
  563. } else {
  564. return substr_replace($subject, $replace, $pos, strlen($search));
  565. }
  566. }
  567. function setSession($name, $email) {
  568. $data = json_encode(array('user' => array(
  569. 'name' => $name,
  570. 'email' => $email,
  571. 'lastLogin' => time()
  572. )));
  573. $hash = session_hash($data);
  574. setcookie('session', $hash . $data, time() + 60 * 60 * 24 * 30, PATH);
  575. }
  576. function getCodeIdParams($request) {
  577. global $home;
  578. $revision = array_pop($request);
  579. $code_id = array_pop($request);
  580. if ($code_id == null || ($home && $home == $code_id)) {
  581. $code_id = $revision;
  582. $revision = 1;
  583. }
  584. return array($code_id, $revision);
  585. }
  586. function getMaxRevision($code_id) {
  587. $sql = sprintf('select max(revision) as rev from sandbox where url="%s"', mysql_real_escape_string($code_id));
  588. $result = mysql_query($sql);
  589. $row = mysql_fetch_object($result);
  590. return $row->rev ? $row->rev : 0;
  591. }
  592. function formatCompletedCode($html, $javascript, $css, $code_id, $revision) {
  593. global $ajax, $quiet;
  594. $javascript = preg_replace('@</script@', "<\/script", $javascript);
  595. if ($quiet && $html) {
  596. $html = '<script>window.onfocus=function(){return false;};window.open=window.print=window.confirm=window.prompt=window.alert=function(){};</script>' . $html;
  597. }
  598. if ($html && stripos($html, '%code%') === false && strlen($javascript)) {
  599. $close = '';
  600. if (stripos($html, '</body>') !== false) {
  601. $parts = explode("</body>", $html);
  602. $html = $parts[0];
  603. $close = count($parts) == 2 ? '</body>' . $parts[1] : '';
  604. }
  605. $html .= "<script>\n" . $javascript . "\n</script>\n" . $close;
  606. } else if ($javascript) {
  607. // removed the regex completely to try to protect $n variables in JavaScript
  608. $htmlParts = explode("%code%", $html);
  609. $html = $htmlParts[0] . $javascript . $htmlParts[1];
  610. $html = preg_replace("/%code%/", $javascript, $html);
  611. }
  612. // repeat for CSS
  613. if ($html && stripos($html, '%css%') === false && strlen($css)) {
  614. $close = '';
  615. if (stripos($html, '</head>') !== false) {
  616. $parts = explode("</head>", $html);
  617. $html = $parts[0];
  618. $close = count($parts) == 2 ? '</head>' . $parts[1] : '';
  619. }
  620. $html .= "<style>\n" . $css . "\n</style>\n" . $close;
  621. } else if ($css) {
  622. // TODO decide whether this is required for the JS
  623. // removed the regex completely to try to protect $n variables in JavaScript
  624. $htmlParts = explode("%css%", $html);
  625. $html = $htmlParts[0] . $css . $htmlParts[1];
  626. $html = preg_replace("/%css%/", $css, $html);
  627. }
  628. if (!$ajax && $code_id != 'jsbin') {
  629. $code_id .= $revision == 1 ? '' : '/' . $revision;
  630. $html = preg_replace('/<html(.*)/', "<html$1\n<!--\n\n Created using " . ROOT . "\n Source can be edited via " . $host . ROOT . "$code_id/edit\n\n-->", $html);
  631. }
  632. return array($html, $javascript, $css);
  633. }
  634. function getCode($code_id, $revision, $testonly = false) {
  635. $sql = sprintf('select * from sandbox where url="%s" and revision="%s"', mysql_real_escape_string($code_id), mysql_real_escape_string($revision));
  636. $result = mysql_query($sql);
  637. if (!mysql_num_rows($result) && $testonly == false) {
  638. header("HTTP/1.1 404 Not Found");
  639. return defaultCode(true);
  640. } else if (!mysql_num_rows($result)) {
  641. return array($revision);
  642. } else {
  643. $row = mysql_fetch_object($result);
  644. // TODO required anymore? used for auto deletion
  645. $sql = 'update sandbox set last_viewed=now() where id=' . $row->id;
  646. mysql_query($sql);
  647. $javascript = preg_replace('/\r/', '', $row->javascript);
  648. $html = preg_replace('/\r/', '', $row->html);
  649. $css = preg_replace('/\r/', '', $row->css);
  650. $revision = $row->revision;
  651. // this is hack, but it's the easiest way of getting the timestamp out
  652. global $last_updated;
  653. $last_updated = $row->created;
  654. // return array(preg_replace('/\r/', '', $html), preg_replace('/\r/', '', $javascript), $row->streaming, $row->active_tab, $row->active_cursor);
  655. return array($revision, get_magic_quotes_gpc() ? stripslashes($html) : $html, get_magic_quotes_gpc() ? stripslashes($javascript) : $javascript, get_magic_quotes_gpc() ? stripslashes($css) : $css);
  656. }
  657. }
  658. function defaultCode($not_found = false) {
  659. $library = '';
  660. global $no_code_found;
  661. if ($not_found) {
  662. $no_code_found = true;
  663. }
  664. $usingRequest = false;
  665. if (isset($_REQUEST['html']) || isset($_REQUEST['js']) || isset($_REQUEST['javascript'])) {
  666. $usingRequest = true;
  667. }
  668. if (@$_REQUEST['html']) {
  669. $html = $_REQUEST['html'];
  670. } else if ($usingRequest) {
  671. $html = '';
  672. } else {
  673. $html = getDefaultCode('html');
  674. }
  675. $javascript = '';
  676. if (@$_REQUEST['js']) {
  677. $javascript = $_REQUEST['js'];
  678. } else if (@$_REQUEST['javascript']) {
  679. $javascript = $_REQUEST['javascript']; // it's beyond me why I ever used js?
  680. } else if ($usingRequest) {
  681. $javascript = '';
  682. } else {
  683. if ($not_found) {
  684. $javascript = 'document.getElementById("hello").innerHTML = "<strong>This URL does not have any code saved to it.</strong>";';
  685. } else {
  686. $javascript = getDefaultCode('javascript');
  687. }
  688. }
  689. $css = '';
  690. if (@$_REQUEST['css']) {
  691. $css = $_REQUEST['css'];
  692. } else {
  693. $css = getDefaultCode('css');
  694. }
  695. return array(0, get_magic_quotes_gpc() ? stripslashes($html) : $html, get_magic_quotes_gpc() ? stripslashes($javascript) : $javascript, get_magic_quotes_gpc() ? stripslashes($css) : $css);
  696. }
  697. function getDefaultCode($prop) {
  698. global $custom;
  699. $ext = $prop === 'javascript' ? 'js' : $prop;
  700. $custom_filename = isset($custom['__dirname']) ? ($custom['__dirname'] . 'default.' . $ext) : null;
  701. $default_filename = '../views/default.' . $ext;
  702. $code = '';
  703. if (file_exists($custom_filename)) {
  704. $code = file_get_contents($custom_filename);
  705. }
  706. else if (file_exists($default_filename)) {
  707. $code = file_get_contents($default_filename);
  708. }
  709. return $code;
  710. }
  711. // I'd consider using a tinyurl type generator, but I've yet to find one.
  712. // this method also produces *pronousable* urls
  713. function generateCodeId($tries = 0) {
  714. $code_id = generateURL();
  715. if ($tries > 2) {
  716. $code_id .= $tries;
  717. }
  718. // check if it's free
  719. $sql = sprintf('select id from sandbox where url="%s"', mysql_real_escape_string($code_id));
  720. $result = mysql_query($sql);
  721. if (mysql_num_rows($result)) {
  722. $code_id = generateCodeId(++$tries);
  723. } else if ($tries > 10) {
  724. echo('Too many tries to find a new code_id - please contact using <a href="/about">about</a>');
  725. exit;
  726. }
  727. return $code_id;
  728. }
  729. function generateURL() {
  730. // generates 5 char word
  731. $vowels = str_split('aeiou');
  732. $const = str_split('bcdfghjklmnpqrstvwxyz');
  733. $word = '';
  734. for ($i = 0; $i < 6; $i++) {
  735. if ($i % 2 == 0) { // even = vowels
  736. $word .= $vowels[rand(0, 4)];
  737. } else {
  738. $word .= $const[rand(0, 20)];
  739. }
  740. }
  741. return $word;
  742. }
  743. function googleAnalytics() {
  744. global $quiet;
  745. if (!$quiet) {
  746. return <<<HERE_DOC
  747. <script>var _gaq=[['_setAccount','UA-1656750-13'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)})(document,'script')</script>
  748. HERE_DOC;
  749. } else {
  750. return '';
  751. }
  752. }
  753. function getTitleFromCode($bin) {
  754. preg_match('/<meta name="description" content="(.*?)"/', $bin['html'], $meta);
  755. preg_match('/<title>(.*?)<\/title>/', $bin['html'], $match);
  756. preg_match('/<body.*?>(.*)/s', $bin['html'], $body);
  757. $title = '';
  758. if (count($meta) && strlen(trim($meta[1]))) {
  759. $title = $meta[1];
  760. if (get_magic_quotes_gpc() && $meta[1]) {
  761. $title = stripslashes($meta[1]);
  762. }
  763. $title = trim(preg_replace('/\s+/', ' ', strip_tags($title)));
  764. } else if (count($body)) {
  765. $title = $body[1];
  766. if (get_magic_quotes_gpc() && $body[1]) {
  767. $title = stripslashes($body[1]);
  768. }
  769. $title = trim(preg_replace('/\s+/', ' ', strip_tags($title)));
  770. }
  771. if (!$title && $bin['javascript']) {
  772. $title = preg_replace('/\s+/', ' ', $bin['javascript']);
  773. }
  774. return $title;
  775. }
  776. function showSaved($name) {
  777. $sql = sprintf('select * from owners where name="%s" order by url, revision desc', mysql_real_escape_string($name));
  778. $result = mysql_query($sql);
  779. $bins = array();
  780. $order = array();
  781. // this is lame, but the optimisation was aweful on the joined version - 3-4 second query
  782. // with a full table scan - not good. I'm worried this doesn't scale properly, but I guess
  783. // I could mitigate this with paging on the UI - just a little...?
  784. if ($result) {
  785. while ($saved = mysql_fetch_object($result)) {
  786. $sql = sprintf('select * from sandbox where url="%s" and revision="%s"', mysql_real_escape_string($saved->url), mysql_real_escape_string($saved->revision));
  787. $binresult = mysql_query($sql);
  788. $bin = mysql_fetch_array($binresult);
  789. if (!isset($bins[$saved->url])) {
  790. $bins[$saved->url] = array();
  791. }
  792. $bins[$saved->url][] = $bin;
  793. if (isset($order[$saved->url])) {
  794. if (@strtotime($order[$saved->url]) < @strtotime($bin['created'])) {
  795. $order[$saved->url] = $bin['created'];
  796. }
  797. } else {
  798. $order[$saved->url] = $bin['created'];
  799. }
  800. }
  801. }
  802. if (count($bins)) {
  803. include_once('list-history.php');
  804. } else {
  805. // echo 'nothing found :(';
  806. // echo 'nothing found';
  807. }
  808. }
  809. function formatURL($code_id, $revision) {
  810. if ($revision != 1 && $revision) {
  811. $code_id .= '/' . $revision;
  812. } else {
  813. $code_id .= '/1';
  814. }
  815. $code_id_path = ROOT;
  816. if ($code_id) {
  817. $code_id_path = ROOT . '/' . $code_id . '/';
  818. }
  819. return $code_id_path;
  820. }
  821. function session_hash($string) {
  822. return sha1($string . SECRET_KEY);
  823. }
  824. ?>