summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron Parecki <aaron@parecki.com>2015-07-25 03:55:00 -0700
committerAaron Parecki <aaron@parecki.com>2015-07-25 03:55:00 -0700
commit35bd5a9e68b50bf57963ea626a3fbf7432b37c93 (patch)
treecc5e9b506494eaf4c59a1200e4bcea875acba46a
parent60830007641c985c11c8ac8f609aab735dd84a96 (diff)
parentafa182bc120371a15b28227ce712cba501a78d05 (diff)
Merge branch 'master' of github.com:aaronpk/IndiePost
-rw-r--r--composer.json3
-rw-r--r--composer.lock75
-rw-r--r--controllers/auth.php3
-rw-r--r--controllers/controllers.php81
-rw-r--r--lib/config.template.php5
-rw-r--r--lib/helpers.php95
-rw-r--r--schema/mysql.sql (renamed from schema/schema.sql)0
-rw-r--r--schema/sqlite.sql22
-rw-r--r--views/layout.php1
-rw-r--r--views/photo.php38
10 files changed, 281 insertions, 42 deletions
diff --git a/composer.json b/composer.json
index cf2f57e..1156a2b 100644
--- a/composer.json
+++ b/composer.json
@@ -10,7 +10,8 @@
"mpratt/relativetime": ">=1.0",
"firebase/php-jwt": "dev-master",
"ruudk/twitter-oauth": "dev-master",
- "andreyco/instagram": "3.*"
+ "andreyco/instagram": "3.*",
+ "p3k/multipart": "*"
},
"autoload": {
"files": [
diff --git a/composer.lock b/composer.lock
index eb3dfc1..819c8ea 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1,9 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
- "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
],
- "hash": "f2f8fdb671b52ce22dc0a5e133f9a13d",
+ "hash": "561c25a6b782004d9b05656de5d67971",
"packages": [
{
"name": "andreyco/instagram",
@@ -51,27 +52,25 @@
{
"name": "firebase/php-jwt",
"version": "dev-master",
- "target-dir": "Firebase/PHP-JWT",
"source": {
"type": "git",
"url": "https://github.com/firebase/php-jwt.git",
- "reference": "83b8899cb73d85d648af93f37ec0ac89f4a5bbae"
+ "reference": "fa8a06e96526eb7c0eeaa47e4f39be59d21f16e1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b8899cb73d85d648af93f37ec0ac89f4a5bbae",
- "reference": "83b8899cb73d85d648af93f37ec0ac89f4a5bbae",
+ "url": "https://api.github.com/repos/firebase/php-jwt/zipball/fa8a06e96526eb7c0eeaa47e4f39be59d21f16e1",
+ "reference": "fa8a06e96526eb7c0eeaa47e4f39be59d21f16e1",
"shasum": ""
},
"require": {
- "php": ">=5.2.0"
+ "php": ">=5.3.0"
},
"type": "library",
"autoload": {
- "classmap": [
- "Authentication/",
- "Exceptions/"
- ]
+ "psr-4": {
+ "Firebase\\JWT\\": "src"
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -91,7 +90,7 @@
],
"description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
"homepage": "https://github.com/firebase/php-jwt",
- "time": "2014-11-18 17:58:25"
+ "time": "2015-07-22 18:31:08"
},
{
"name": "indieauth/client",
@@ -365,16 +364,16 @@
},
{
"name": "mpratt/relativetime",
- "version": "1.0",
+ "version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/mpratt/RelativeTime.git",
- "reference": "5dd7078d2bc830227c1f5a0081c68c323fb18555"
+ "reference": "219e6568fa3e7b181244f93be493fbab4c89c056"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/mpratt/RelativeTime/zipball/5dd7078d2bc830227c1f5a0081c68c323fb18555",
- "reference": "5dd7078d2bc830227c1f5a0081c68c323fb18555",
+ "url": "https://api.github.com/repos/mpratt/RelativeTime/zipball/219e6568fa3e7b181244f93be493fbab4c89c056",
+ "reference": "219e6568fa3e7b181244f93be493fbab4c89c056",
"shasum": ""
},
"require": {
@@ -409,7 +408,43 @@
"time",
"time-ago"
],
- "time": "2013-09-23 22:51:48"
+ "time": "2015-05-28 14:13:23"
+ },
+ {
+ "name": "p3k/multipart",
+ "version": "0.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/aaronpk/php-multipart-encoder.git",
+ "reference": "f5400011b20046cebbdfed686d051fb2aa600a14"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/aaronpk/php-multipart-encoder/zipball/f5400011b20046cebbdfed686d051fb2aa600a14",
+ "reference": "f5400011b20046cebbdfed686d051fb2aa600a14",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">5.4.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/p3k/Multipart.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache 2.0"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Parecki",
+ "homepage": "http://aaronparecki.com"
+ }
+ ],
+ "description": "Multipart Encoding Library",
+ "time": "2015-07-16 19:28:02"
},
{
"name": "ruudk/twitter-oauth",
@@ -486,12 +521,12 @@
"version": "2.2.0",
"source": {
"type": "git",
- "url": "https://github.com/codeguy/Slim.git",
+ "url": "https://github.com/slimphp/Slim.git",
"reference": "b8181de1112a1e2f565b40158b621c34ded38053"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/codeguy/Slim/zipball/b8181de1112a1e2f565b40158b621c34ded38053",
+ "url": "https://api.github.com/repos/slimphp/Slim/zipball/b8181de1112a1e2f565b40158b621c34ded38053",
"reference": "b8181de1112a1e2f565b40158b621c34ded38053",
"shasum": ""
},
@@ -533,6 +568,8 @@
"firebase/php-jwt": 20,
"ruudk/twitter-oauth": 20
},
+ "prefer-stable": false,
+ "prefer-lowest": false,
"platform": [],
"platform-dev": []
}
diff --git a/controllers/auth.php b/controllers/auth.php
index 3611416..33baa3e 100644
--- a/controllers/auth.php
+++ b/controllers/auth.php
@@ -64,7 +64,8 @@ $app->get('/', function($format='html') use($app) {
ob_start();
render('index', array(
'title' => 'Quill',
- 'meta' => ''
+ 'meta' => '',
+ 'authorizing' => false
));
$html = ob_get_clean();
$res->body($html);
diff --git a/controllers/controllers.php b/controllers/controllers.php
index b444676..72ac2b8 100644
--- a/controllers/controllers.php
+++ b/controllers/controllers.php
@@ -72,7 +72,8 @@ $app->get('/new', function() use($app) {
'response_date' => $user->last_micropub_response_date,
'syndication_targets' => json_decode($user->syndication_targets, true),
'test_response' => $test_response,
- 'location_enabled' => $user->location_enabled
+ 'location_enabled' => $user->location_enabled,
+ 'authorizing' => false
));
$app->response()->body($html);
}
@@ -103,7 +104,8 @@ $app->get('/bookmark', function() use($app) {
'bookmark_content' => $content,
'bookmark_tags' => $tags,
'token' => generate_login_token(),
- 'syndication_targets' => json_decode($user->syndication_targets, true)
+ 'syndication_targets' => json_decode($user->syndication_targets, true),
+ 'authorizing' => false
));
$app->response()->body($html);
}
@@ -121,7 +123,21 @@ $app->get('/favorite', function() use($app) {
$html = render('new-favorite', array(
'title' => 'New Favorite',
'url' => $url,
- 'token' => generate_login_token()
+ 'token' => generate_login_token(),
+ 'authorizing' => false
+ ));
+ $app->response()->body($html);
+ }
+});
+
+$app->get('/photo', function() use($app) {
+ if($user=require_login($app)) {
+ $params = $app->request()->params();
+
+ $html = render('photo', array(
+ 'title' => 'New Photo',
+ 'note_content' => '',
+ 'authorizing' => false
));
$app->response()->body($html);
}
@@ -139,7 +155,8 @@ $app->get('/repost', function() use($app) {
$html = render('new-repost', array(
'title' => 'New Repost',
'url' => $url,
- 'token' => generate_login_token()
+ 'token' => generate_login_token(),
+ 'authorizing' => false
));
$app->response()->body($html);
}
@@ -160,17 +177,17 @@ $app->get('/creating-a-token-endpoint', function() use($app) {
$app->redirect('http://indiewebcamp.com/token-endpoint', 301);
});
$app->get('/creating-a-micropub-endpoint', function() use($app) {
- $html = render('creating-a-micropub-endpoint', array('title' => 'Creating a Micropub Endpoint'));
+ $html = render('creating-a-micropub-endpoint', array('title' => 'Creating a Micropub Endpoint', 'authorizing' => false));
$app->response()->body($html);
});
$app->get('/docs', function() use($app) {
- $html = render('docs', array('title' => 'Documentation'));
+ $html = render('docs', array('title' => 'Documentation', 'authorizing' => false));
$app->response()->body($html);
});
$app->get('/privacy', function() use($app) {
- $html = render('privacy', array('title' => 'Quill Privacy Policy'));
+ $html = render('privacy', array('title' => 'Quill Privacy Policy', 'authorizing' => false));
$app->response()->body($html);
});
@@ -217,7 +234,7 @@ $app->get('/add-to-home', function() use($app) {
$app->get('/settings', function() use($app) {
if($user=require_login($app)) {
- $html = render('settings', array('title' => 'Settings', 'include_facebook' => true));
+ $html = render('settings', array('title' => 'Settings', 'include_facebook' => true, 'authorizing' => false));
$app->response()->body($html);
}
});
@@ -282,6 +299,20 @@ function create_favorite(&$user, $url) {
return $r;
}
+function create_photo(&$user, $params, $file) {
+ $error = validate_photo($file);
+
+ if(!$error) {
+ $file_path = $file['tmp_name'];
+ $micropub_request = array('content' => $params['note_content']);
+ $r = micropub_post_for_user($user, $micropub_request, $file_path);
+ } else {
+ $r = array('error' => $error);
+ }
+
+ return $r;
+}
+
function create_repost(&$user, $url) {
$micropub_request = array(
'repost-of' => $url
@@ -336,6 +367,40 @@ $app->post('/favorite', function() use($app) {
}
});
+$app->post('/photo', function() use($app) {
+ if($user=require_login($app)) {
+
+ // var_dump($app->request()->post());
+ //
+ // Since $app->request()->post() with multipart is always
+ // empty (bug in Slim?) We're using the raw $_POST here
+ // until this gets fixed.
+ // PHP empties everything in $_POST if the file upload size exceeds
+ // that is why we have to test if the variables exist first.
+
+ $note_content = isset($_POST['note_content']) ? $_POST['note_content'] : null;
+ $params = array('note_content' => $note_content);
+ $file = isset($_FILES['note_photo']) ? $_FILES['note_photo'] : null;
+
+ $r = create_photo($user, $params, $file);
+
+ // Populate the error if there was no location header.
+ if(empty($r['location']) && empty($r['error'])) {
+ $r['error'] = "No 'Location' header in response.";
+ }
+
+ $html = render('photo', array(
+ 'title' => 'Photo posted',
+ 'note_content' => $params['note_content'],
+ 'location' => (isset($r['location']) ? $r['location'] : null),
+ 'error' => (isset($r['error']) ? $r['error'] : null),
+ 'response' => (isset($r['response']) ? htmlspecialchars($r['response']) : null),
+ 'authorizing' => false
+ ));
+ $app->response()->body($html);
+ }
+});
+
$app->post('/repost', function() use($app) {
if($user=require_login($app)) {
$params = $app->request()->params();
diff --git a/lib/config.template.php b/lib/config.template.php
index dae8968..ee822bb 100644
--- a/lib/config.template.php
+++ b/lib/config.template.php
@@ -4,11 +4,16 @@ class Config {
public static $base_url = 'http://quill.dev/';
public static $gaid = '';
+ // MySQL (default)
public static $dbHost = '127.0.0.1';
public static $dbName = 'quill';
public static $dbUsername = 'quill';
public static $dbPassword = '';
+ // Sqlite
+ // public static $dbType = 'sqlite';
+ // public static $dbFilePath = './example.db';
+
public static $jwtSecret = 'xxx';
public static $fbClientID = '';
diff --git a/lib/helpers.php b/lib/helpers.php
index 4f6b4c1..eb8994c 100644
--- a/lib/helpers.php
+++ b/lib/helpers.php
@@ -1,8 +1,12 @@
<?php
-ORM::configure('mysql:host=' . Config::$dbHost . ';dbname=' . Config::$dbName);
-ORM::configure('username', Config::$dbUsername);
-ORM::configure('password', Config::$dbPassword);
+if(Config::$dbType == 'sqlite') {
+ ORM::configure('sqlite:' . Config::$dbFilePath);
+} else {
+ ORM::configure('mysql:host=' . Config::$dbHost . ';dbname=' . Config::$dbName);
+ ORM::configure('username', Config::$dbUsername);
+ ORM::configure('password', Config::$dbPassword);
+}
function render($page, $data) {
global $app;
@@ -70,9 +74,9 @@ function get_timezone($lat, $lng) {
return null;
}
-function micropub_post_for_user(&$user, $params) {
+function micropub_post_for_user(&$user, $params, $file_path = NULL) {
// Now send to the micropub endpoint
- $r = micropub_post($user->micropub_endpoint, $params, $user->micropub_access_token);
+ $r = micropub_post($user->micropub_endpoint, $params, $user->micropub_access_token, $file_path);
$user->last_micropub_response = substr(json_encode($r), 0, 1024);
$user->last_micropub_response_date = date('Y-m-d H:i:s');
@@ -90,21 +94,33 @@ function micropub_post_for_user(&$user, $params) {
return $r;
}
-function micropub_post($endpoint, $params, $access_token) {
+function micropub_post($endpoint, $params, $access_token, $file_path = NULL) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $endpoint);
- curl_setopt($ch, CURLOPT_HTTPHEADER, array(
- 'Authorization: Bearer ' . $access_token
- ));
curl_setopt($ch, CURLOPT_POST, true);
- $post = http_build_query(array_merge(array(
- 'h' => 'entry'
- ), $params));
- $post = preg_replace('/%5B[0-9]+%5D/', '%5B%5D', $post); // change [0] to []
+
+ $httpheaders = array('Authorization: Bearer ' . $access_token);
+ $params = array_merge(array('h' => 'entry'), $params);
+
+ if(!$file_path) {
+ $post = http_build_query($params);
+ $post = preg_replace('/%5B[0-9]+%5D/', '%5B%5D', $post); // change [0] to []
+ } else {
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+ $mimetype = finfo_file($finfo, $file_path);
+ $multipart = new p3k\Multipart();
+ $multipart->addArray($params);
+ $multipart->addFile('photo', $file_path, $mimetype);
+ $post = $multipart->data();
+ array_push($httpheaders, 'Content-Type: ' . $multipart->contentType());
+ }
+
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheaders);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
+
$response = curl_exec($ch);
$error = curl_error($ch);
$sent_headers = curl_getinfo($ch, CURLINFO_HEADER_OUT);
@@ -215,4 +231,57 @@ function instagram_client() {
));
}
+function validate_photo(&$file) {
+ try {
+
+ if ($_SERVER['REQUEST_METHOD'] == 'POST' && count($_POST) < 1 ) {
+ throw new RuntimeException('File upload size exceeded.');
+ }
+
+ // Undefined | Multiple Files | $_FILES Corruption Attack
+ // If this request falls under any of them, treat it invalid.
+ if (
+ !isset($file['error']) ||
+ is_array($file['error'])
+ ) {
+ throw new RuntimeException('Invalid parameters.');
+ }
+
+ // Check $file['error'] value.
+ switch ($file['error']) {
+ case UPLOAD_ERR_OK:
+ break;
+ case UPLOAD_ERR_NO_FILE:
+ throw new RuntimeException('No file sent.');
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ throw new RuntimeException('Exceeded filesize limit.');
+ default:
+ throw new RuntimeException('Unknown errors.');
+ }
+ // You should also check filesize here.
+ if ($file['size'] > 1000000) {
+ throw new RuntimeException('Exceeded filesize limit.');
+ }
+
+ // DO NOT TRUST $file['mime'] VALUE !!
+ // Check MIME Type by yourself.
+ $finfo = new finfo(FILEINFO_MIME_TYPE);
+ if (false === $ext = array_search(
+ $finfo->file($file['tmp_name']),
+ array(
+ 'jpg' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif',
+ ),
+ true
+ )) {
+ throw new RuntimeException('Invalid file format.');
+ }
+
+ } catch (RuntimeException $e) {
+
+ return $e->getMessage();
+ }
+} \ No newline at end of file
diff --git a/schema/schema.sql b/schema/mysql.sql
index 5f33ce6..5f33ce6 100644
--- a/schema/schema.sql
+++ b/schema/mysql.sql
diff --git a/schema/sqlite.sql b/schema/sqlite.sql
new file mode 100644
index 0000000..ac691e3
--- /dev/null
+++ b/schema/sqlite.sql
@@ -0,0 +1,22 @@
+CREATE TABLE users (
+ id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ url TEXT,
+ authorization_endpoint TEXT,
+ token_endpoint TEXT,
+ micropub_endpoint TEXT,
+ micropub_access_token TEXT,
+ micropub_scope TEXT,
+ micropub_response TEXT,
+ micropub_success INTEGER default 0,
+ date_created datetime,
+ last_login datetime,
+ last_micropub_response TEXT,
+ last_micropub_response_date datetime,
+ location_enabled INTEGER NOT NULL default 0,
+ syndication_targets TEXT,
+ facebook_access_token TEXT,
+ twitter_access_token TEXT,
+ twitter_token_secret TEXT,
+ twitter_username TEXT,
+ instagram_access_token TEXT
+); \ No newline at end of file
diff --git a/views/layout.php b/views/layout.php
index 072dea6..7c2dab9 100644
--- a/views/layout.php
+++ b/views/layout.php
@@ -66,6 +66,7 @@ if(property_exists($this, 'include_facebook')) {
<li><a href="/new">New Note</a></li>
<li><a href="/bookmark">Bookmark</a></li>
<li><a href="/favorite">Favorite</a></li>
+ <li><a href="/photo">Photo</a></li>
<? } ?>
<li><a href="/docs">Docs</a></li>
diff --git a/views/photo.php b/views/photo.php
new file mode 100644
index 0000000..364a55d
--- /dev/null
+++ b/views/photo.php
@@ -0,0 +1,38 @@
+ <div class="narrow">
+ <?= partial('partials/header') ?>
+
+ <form method="POST" role="form" style="margin-top: 20px;" id="note_form" enctype="multipart/form-data">
+
+ <div class="form-group">
+ <label for="note_photo"><code>photo</code></label>
+ <input type="file" name="note_photo" id="note_photo" accept="image/jpg,image/jpeg,image/gif,image/png">
+ <p class="help-block">Photo JPEG, GIF or PNG.</p>
+ </div>
+
+ <div class="form-group">
+ <label for="note_content"><code>content</code> (optional)</label>
+ <textarea name="note_content" id="note_content" value="" class="form-control" style="height: 4em;"><? if(isset($this->note_content)) echo $this->note_content ?></textarea>
+ </div>
+
+ <button class="btn btn-success" id="btn_post">Post</button>
+ </form>
+
+ <? if(!empty($this->location)): ?>
+ <div class="alert alert-success">
+ <strong>Success!</strong> Photo posted to: <em><a href="<?= $this->location ?>"><?= $this->location ?></a></em>
+ </div>
+ <? endif ?>
+
+ <? if(!empty($this->error)): ?>
+ <div class="alert alert-danger">
+ <strong>Error:</strong> <em><?= $this->error ?></em>
+ </div>
+ <? endif ?>
+
+ <? if(!empty($this->response)): ?>
+ <h4>Response:</h4>
+ <pre><?= $this->response ?></pre>
+ <? endif ?>
+
+
+ </div> \ No newline at end of file