From 8ee6bfe97633d31c6b89cebbc434837eca04d6dd Mon Sep 17 00:00:00 2001 From: Christian Weiske Date: Thu, 23 Oct 2014 23:07:01 +0200 Subject: [PATCH] note preview --- appinfo/database.xml | 1 + appinfo/routes.php | 5 + controller/guicontroller.php | 48 ++++++ grauphel.css | 30 ++++ lib/converter/html.php | 197 ++++++++++++++++++++++++ lib/notestorage.php | 22 +++ templates/gui-note.php | 17 ++ templates/tag.php | 28 +++- tests/Lib/Converter/HtmlTest.php | 30 ++++ tests/data/formattest.html | 36 +++++ tests/data/formattest.tomboynotecontent | 36 +++++ tests/data/xss.html | 11 ++ tests/data/xss.tomboynotecontent | 11 ++ 13 files changed, 465 insertions(+), 7 deletions(-) create mode 100644 lib/converter/html.php create mode 100644 templates/gui-note.php create mode 100644 tests/Lib/Converter/HtmlTest.php create mode 100644 tests/data/formattest.html create mode 100644 tests/data/formattest.tomboynotecontent create mode 100644 tests/data/xss.html create mode 100644 tests/data/xss.tomboynotecontent diff --git a/appinfo/database.xml b/appinfo/database.xml index 7419ee7..4738d60 100755 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -1,5 +1,6 @@ + *dbname* true false diff --git a/appinfo/routes.php b/appinfo/routes.php index 19a4b93..ddfc90b 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -68,6 +68,11 @@ $application->registerRoutes( 'name' => 'gui#tag', 'verb' => 'GET', ), + array( + 'url' => '/note/{guid}', + 'name' => 'gui#note', + 'verb' => 'GET', + ), array( 'url' => '/tokens', 'name' => 'gui#tokens', diff --git a/controller/guicontroller.php b/controller/guicontroller.php index 6f0a15c..97a7f10 100644 --- a/controller/guicontroller.php +++ b/controller/guicontroller.php @@ -17,6 +17,7 @@ use \OCP\AppFramework\Controller; use \OCP\AppFramework\Http\TemplateResponse; use \OCA\Grauphel\Lib\Client; use \OCA\Grauphel\Lib\TokenStorage; +use \OCA\Grauphel\Lib\Response\ErrorResponse; /** * Owncloud frontend @@ -72,6 +73,47 @@ class GuiController extends Controller return $res; } + /** + * Show contents of a note + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function note($guid) + { + $res = new TemplateResponse('grauphel', 'gui-note'); + + $note = $this->getNotes()->load($guid, false); + if ($note === null) { + return new ErrorResponse('Note does not exist'); + } + + $converter = new \OCA\Grauphel\Lib\Converter\Html(); + $converter->internalLinkHandler = array($this, 'noteLinkHandler'); + $res->setParams( + array( + 'note' => $note, + 'note-content' => $converter->convert( + $note->{'note-content'} + ), + ) + ); + + $this->addNavigation($res); + return $res; + } + + public function noteLinkHandler($noteTitle) + { + $guid = $this->getNotes()->loadGuidByTitle($noteTitle); + if ($guid === null) { + return '#'; + } + return $this->urlGen->linkToRoute( + 'grauphel.gui.note', array('guid' => $guid) + ); + } + /** * Show all notes of a tag * @@ -81,6 +123,12 @@ class GuiController extends Controller public function tag($rawtag) { $notes = $this->getNotes()->loadNotesOverview(null, $rawtag); + usort( + $notes, + function($noteA, $noteB) { + return strcmp($noteA['title'], $noteB['title']); + } + ); $res = new TemplateResponse('grauphel', 'tag'); $res->setParams( diff --git a/grauphel.css b/grauphel.css index 6ba1cde..1ad5a2d 100644 --- a/grauphel.css +++ b/grauphel.css @@ -135,6 +135,9 @@ a.action.delete, table.table form button.action.delete { a.action { line-height: 30px; } +a.cellclick { + display: block; +} table.table form { display: inline; } @@ -142,3 +145,30 @@ table.table form button.action { border: none; background-color: transparent; } + +.muted { + color: #999; +} + +.note-content { + padding-top: 2ex; +} + +.note-content .strikethrough { + text-decoration: line-through; +} +.note-content .highlight { + background-color: yellow; +} +.note-content .small { + font-size: 80%; +} +.note-content .large { + font-size: 150%; +} +.note-content .huge { + font-size: 200%; +} +.note-content a { + text-decoration: underline; +} \ No newline at end of file diff --git a/lib/converter/html.php b/lib/converter/html.php new file mode 100644 index 0000000..1723a5b --- /dev/null +++ b/lib/converter/html.php @@ -0,0 +1,197 @@ + + * @copyright 2014 Christian Weiske + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 + * @link http://cweiske.de/grauphel.htm + */ +namespace OCA\Grauphel\Lib\Converter; +use \XMLReader; + +/** + * Convert Tomboy note XML to HTML + * + * Tomboy already ships with a converter: + * https://git.gnome.org/browse/tomboy/tree/Tomboy/Addins/ExportToHtml/ExportToHtml.xsl + * We cannot use it since we want nice callbacks, and we do not want to rely + * on the PHP XSL extension, and we have to fix the links. + * + * @category Tools + * @package Grauphel + * @author Christian Weiske + * @copyright 2014 Christian Weiske + * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 + * @version Release: @package_version@ + * @link http://cweiske.de/grauphel.htm + */ +class Html +{ + protected static $tagMap = array( + 'list' => 'ul', + 'list-item' => 'li', + 'bold' => 'b', + 'italic' => 'i', + 'monospace' => 'tt', + ); + + protected static $styleClassMap = array( + 'strikethrough' => 'strikethrough', + 'highlight' => 'highlight', + 'size:small' => 'small', + 'size:large' => 'large', + 'size:huge' => 'huge', + ); + + public $internalLinkHandler; + + + + public function __construct() + { + $this->internalLinkHandler = array($this, 'internalLinkHandler'); + } + + /** + * Converts the tomboy note XML into HTML + * + * @param string $xmlContent Tomboy note content + * + * @return string HTML + */ + public function convert($xmlContent) + { + if (strpos($xmlContent, '') !== false) { + $xmlContent = $this->fixNastyLinks($xmlContent); + } + + $html = ''; + $reader = new XMLReader(); + $reader->xml( + '' . "\n" + . '' + . $xmlContent + . '' + ); + + $withinLink = false; + $store = &$html; + while ($reader->read()) { + switch ($reader->nodeType) { + case XMLReader::ELEMENT: + //echo $reader->name . "\n"; + if (isset(static::$tagMap[$reader->name])) { + $store .= '<' . static::$tagMap[$reader->name] . '>'; + } else if (isset(static::$styleClassMap[$reader->name])) { + $store .= ''; + } else if (substr($reader->name, 0, 5) == 'link:') { + $withinLink = true; + $linkText = ''; + $store = &$linkText; + } + break; + case XMLReader::END_ELEMENT: + if (isset(static::$tagMap[$reader->name])) { + $store .= 'name] . '>'; + } else if (isset(static::$styleClassMap[$reader->name])) { + $store .= ''; + } else if (substr($reader->name, 0, 5) == 'link:') { + $withinLink = false; + $store = &$html; + $linkUrl = htmlspecialchars_decode(strip_tags($linkText)); + if ($reader->name == 'link:internal') { + $linkUrl = call_user_func($this->internalLinkHandler, $linkUrl); + } else { + $linkUrl = $this->fixLinkUrl($linkUrl); + } + $store .= '' + . $linkText + . ''; + } + break; + case XMLReader::TEXT: + case XMLReader::SIGNIFICANT_WHITESPACE: + $store .= nl2br(htmlspecialchars($reader->value)); + break; + default: + throw new \Exception( + 'Unsupported XML node type: ' . $reader->nodeType + ); + } + } + + $html = str_replace("
\n", "\n", $html); + + return $html; + } + + /** + * Fixes external URLs without a protocol + * + * @param string $linkUrl URL to fix + * + * @return string Fixed URL + */ + protected function fixLinkUrl($linkUrl) + { + if ($linkUrl{0} == '/') { + //Unix file path + $linkUrl = 'file://' . $linkUrl; + } + return $linkUrl; + } + + /** + * Dummy internal link handler that simply adds ".htm" to the note title + * + * @param string $linkUrl Title of page that is linked + * + * @return string URL to link to + */ + public function internalLinkHandler($linkUrl) + { + return $linkUrl . '.htm'; + } + + /** + * Re-arranges the XML of formatted links to that clean link tags can + * be generated. + * + * Tomboy 1.15.2 allows link formatting, and the resulting XML is a + * mess of multiple(!) link tags that are within or around other formatting + * tags. + * + * This method tries to re-arrange the links so that only a single link tag + * appears with all the formatting inside. + * + * @param string $xmlContent Tomboy note content + * + * @return string XML content, with re-arranged link tags. + */ + protected function fixNastyLinks($xmlContent) + { + preg_match_all( + '#(?:<.*>)?.+.+#U', + $xmlContent, + $matches + ); + + foreach ($matches[0] as $nastyLink) { + $cleaner = str_replace('
', '', $nastyLink); + $cleaner = preg_replace('#<([a-z]+)><(link:internal)>#U', '<\2><\1>', $cleaner); + $cleaner = preg_replace('##U', '', $cleaner); + $cleaner = str_replace('', '', $cleaner); + $xmlContent = str_replace($nastyLink, $cleaner, $xmlContent); + } + + return $xmlContent; + } +} +?> diff --git a/lib/notestorage.php b/lib/notestorage.php index fb68030..7ecf049 100644 --- a/lib/notestorage.php +++ b/lib/notestorage.php @@ -245,6 +245,28 @@ class NoteStorage return $this->noteFromRow($row); } + /** + * Load a GUID of a note by the note title + * + * @param string $title Note title + * + * @return string GUID, NULL if note could not be found + */ + public function loadGuidByTitle($title) + { + $row = \OC_DB::executeAudited( + 'SELECT note_guid FROM `*PREFIX*grauphel_notes`' + . ' WHERE `note_user` = ? AND `note_title` = ?', + array($this->username, $title) + )->fetchRow(); + + if ($row === false) { + return null; + } + + return $row['note_guid']; + } + /** * Save a note into storage. * diff --git a/templates/gui-note.php b/templates/gui-note.php new file mode 100644 index 0000000..a7e0587 --- /dev/null +++ b/templates/gui-note.php @@ -0,0 +1,17 @@ + + + +printPage(); ?> + + + +
+

title); ?>

+

+ Last modified: + {'last-change-date'}))); ?> +

+
+ +
+
diff --git a/templates/tag.php b/templates/tag.php index 68fa813..8c7eecc 100644 --- a/templates/tag.php +++ b/templates/tag.php @@ -3,14 +3,28 @@ printPage(); ?> -
+

Notebook:

-

- Work in progress: You can't do anything here. -

-
    + + + + + + + + + + -
  • + + + + - + + +
    TitleLast change
    + + +
diff --git a/tests/Lib/Converter/HtmlTest.php b/tests/Lib/Converter/HtmlTest.php new file mode 100644 index 0000000..0a66ee4 --- /dev/null +++ b/tests/Lib/Converter/HtmlTest.php @@ -0,0 +1,30 @@ +convert($input); + $this->assertEquals( + file_get_contents(__DIR__ . '/../../data/formattest.html'), + $output + ); + } + + public function testXSS() + { + $input = file_get_contents(__DIR__ . '/../../data/xss.tomboynotecontent'); + + $converter = new OCA\Grauphel\Lib\Converter\Html(); + $output = $converter->convert($input); + $this->assertEquals( + file_get_contents(__DIR__ . '/../../data/xss.html'), + $output + ); + } +} +?> \ No newline at end of file diff --git a/tests/data/formattest.html b/tests/data/formattest.html new file mode 100644 index 0000000..5bc3709 --- /dev/null +++ b/tests/data/formattest.html @@ -0,0 +1,36 @@ +Eine Zeile Text.
+Zeilenumbruch.
+Noch ein Zeilenumbruch.
+
+Eine Leerzeile obendrüber.
+
+Jetzt kommt eine Liste:
+
  • eins
    +
  • zwei
    +
    • zwei-eins
      +
      • zwei-eins-eins
        +
      • zwei-eins-zwei
        +
    • zwei-zwei
      +
  • drei
    +
    • drei-eins
      +
      • drei-eins-eins
        +
        • drei-eins-eins-eins
+Formatierungen:
+Fetter Text
+Kursiver Text
+Durchgestrichener Text
+Hervorgehobener Text
+Feste Breite
+
+Schriftgrößen:
+klein
+normal
+groß
+riesig
+
+Links:
+ +
+Ende.
diff --git a/tests/data/formattest.tomboynotecontent b/tests/data/formattest.tomboynotecontent new file mode 100644 index 0000000..8f0b017 --- /dev/null +++ b/tests/data/formattest.tomboynotecontent @@ -0,0 +1,36 @@ +Eine Zeile Text. +Zeilenumbruch. +Noch ein Zeilenumbruch. + +Eine Leerzeile obendrüber. + +Jetzt kommt eine Liste: +eins +zwei +zwei-eins +zwei-eins-eins +zwei-eins-zwei +zwei-zwei +drei +drei-eins +drei-eins-eins +drei-eins-eins-eins +Formatierungen: +Fetter Text +Kursiver Text +Durchgestrichener Text +Hervorgehobener Text +Feste Breite + +Schriftgrößen: +klein +normal +groß +riesig + +Links: +Test +http://cweiske.de/?foo#bar +/home/cweiske/fam.jpg + +Ende. diff --git a/tests/data/xss.html b/tests/data/xss.html new file mode 100644 index 0000000..1908ce2 --- /dev/null +++ b/tests/data/xss.html @@ -0,0 +1,11 @@ +<h1>Hallo!</h1>
+Was ist denn <a href="foo">bar
+
+Link mit fett drin: Formattest.
+
  • Liste
    +
  • ein bisschen fett
    +
  • monospaced
    +
  • </li></ul></div></div></div></div></div>
+
+Link zum Formattest.
+Link mit Sonder">zeichen.
diff --git a/tests/data/xss.tomboynotecontent b/tests/data/xss.tomboynotecontent new file mode 100644 index 0000000..6a86314 --- /dev/null +++ b/tests/data/xss.tomboynotecontent @@ -0,0 +1,11 @@ +<h1>Hallo!</h1> +Was ist denn <a href="foo">bar + +Link mit fett drin: Formattest. +Liste +ein bisschen fett +monospaced +</li></ul></div></div></div></div></div> + +Link zum Formattest. +Link mit Sonder">zeichen. -- 2.30.2