Browse Source

Merge branch 'master' of github.com:aaronpk/IndiePost

pull/25/head
Aaron Parecki 6 years ago
parent
commit
35bd5a9e68
10 changed files with 281 additions and 42 deletions
  1. +2
    -1
      composer.json
  2. +56
    -19
      composer.lock
  3. +2
    -1
      controllers/auth.php
  4. +73
    -8
      controllers/controllers.php
  5. +5
    -0
      lib/config.template.php
  6. +82
    -13
      lib/helpers.php
  7. +0
    -0
      schema/mysql.sql
  8. +22
    -0
      schema/sqlite.sql
  9. +1
    -0
      views/layout.php
  10. +38
    -0
      views/photo.php

+ 2
- 1
composer.json View File

@ -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": [

+ 56
- 19
composer.lock View File

@ -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": []
}

+ 2
- 1
controllers/auth.php View File

@ -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);

+ 73
- 8
controllers/controllers.php View File

@ -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();

+ 5
- 0
lib/config.template.php View File

@ -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 = '';

+ 82
- 13
lib/helpers.php View File

@ -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();
}
}

schema/schema.sql → schema/mysql.sql View File


+ 22
- 0
schema/sqlite.sql View File

@ -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
);

+ 1
- 0
views/layout.php View File

@ -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>

+ 38
- 0
views/photo.php View File

@ -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>

Loading…
Cancel
Save