From f035fa3464ad4b3ffc7a63fac6fa706041f87a49 Mon Sep 17 00:00:00 2001 From: Max Pozdeev Date: Wed, 21 Oct 2020 23:47:32 +0300 Subject: [PATCH] * do not allow urls and any html elements in task title (escape all); format the note in php (not js) using mttMarkup_v1 --- src/ajax.php | 6 +++--- src/content/themes/default/style.css | 4 ++-- src/feed.php | 8 ++++---- src/includes/common.php | 15 +++++++++++++-- src/includes/mytinytodo.js | 28 +++++++++++++++++----------- 5 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/ajax.php b/src/ajax.php index 654d9c4..68cd367 100644 --- a/src/ajax.php +++ b/src/ajax.php @@ -231,7 +231,7 @@ elseif(isset($_GET['editNote'])) $db->dq("UPDATE {$db->prefix}todolist SET note=?,d_edited=? WHERE id=$id", array($note, time()) ); $t = array(); $t['total'] = 1; - $t['list'][] = array('id'=>$id, 'note'=>nl2br(escapeTags($note)), 'noteText'=>(string)$note); + $t['list'][] = array('id'=>$id, 'note'=>mttMarkup_v1($note), 'noteText'=>(string)$note); jsonExit($t); } elseif(isset($_GET['editTask'])) @@ -544,7 +544,7 @@ function prepareTaskRow($r) return array( 'id' => $r['id'], - 'title' => escapeTags($r['title']), + 'title' => htmlspecialchars( $r['title'] ), 'listId' => $r['list_id'], 'date' => htmlarray($dCreated), 'dateInt' => (int)$r['d_created'], @@ -556,7 +556,7 @@ function prepareTaskRow($r) 'dateCompletedInlineTitle' => htmlarray(sprintf($lang->get('taskdate_inline_completed'), $dCompleted)), 'compl' => (int)$r['compl'], 'prio' => $r['prio'], - 'note' => nl2br(escapeTags($r['note'])), + 'note' => mttMarkup_v1($r['note']), 'noteText' => (string)$r['note'], 'ow' => (int)$r['ow'], 'tags' => htmlarray($r['tags']), diff --git a/src/content/themes/default/style.css b/src/content/themes/default/style.css index 48a1282..0a41b66 100644 --- a/src/content/themes/default/style.css +++ b/src/content/themes/default/style.css @@ -226,8 +226,8 @@ li.task-completed .task-note-block .task-note { text-decoration:line-through; } .invisible { visibility:hidden; } .in500 { width:500px; color:#444444; } .in100 { width:100px; color:#444444; } -.task-note span a { color:#777777; } -.task-note span a:hover { color:#af0000; } +.task-note a { color:#777777; } +.task-note a:hover { color:#af0000; } .task-prio { padding-left:2px; padding-right:2px; margin-left:0px; margin-right:5px; cursor:default; } .prio-neg { background-color:#3377ff; color:#ffffff; } diff --git a/src/feed.php b/src/feed.php index 59d6c77..b9a7d1c 100644 --- a/src/feed.php +++ b/src/feed.php @@ -58,9 +58,9 @@ while($r = $q->fetch_assoc($q)) } if($r['tags'] != '') $a[] = $lang->get('tags'). ": ". str_replace(',', ', ', $r['tags']); if($r['compl']) $a[] = $lang->get('taskdate_completed'). ": ". timestampToDatetime($r['d_completed']); - $r['title'] = strip_tags($r['title']); - $r['note'] = escapeTags($r['note']); - $r['_descr'] = nl2br($r['note']). ($a && $r['note']!='' ? "

" : ""). implode("
", htmlarray($a)); + $r['title'] = htmlspecialchars( $r['title'] ); + $r['note'] = mttMarkup_v1($r['note']); + $r['_descr'] = $r['note']. ($a && $r['note']!='' ? "

" : ""). implode("
", htmlarray($a)); $data[] = $r; } @@ -81,7 +81,7 @@ function printRss($listData, $data) $d = gmdate('r', $v[$listData['_uid_field']]); $guid = $listData['id'].'-'.$v['id'].'-'.$v[$listData['_uid_field']]; - $s .= "\n<![CDATA[". str_replace("]]>", "]]]]><![CDATA[>", $v['title']). "]]>\n". + $s .= "\n${v['title']}\n". "$link\n". "$d\n". "\n". diff --git a/src/includes/common.php b/src/includes/common.php index 67384bb..57cca25 100644 --- a/src/includes/common.php +++ b/src/includes/common.php @@ -212,15 +212,26 @@ function url_dir($url, $onlyPath = 1) return '/'; } -function escapeTags($s) +// Convert note's raw text to html with allowed elements (b,i,u,s and raw urls) +function mttMarkup_v1($s) { + //hide allowed elements from escaping $c1 = chr(1); $c2 = chr(2); $s = preg_replace("~([\s\S]*?)~i", "${c1}b${c2}\$1${c1}/b${c2}", $s); $s = preg_replace("~([\s\S]*?)~i", "${c1}i${c2}\$1${c1}/i${c2}", $s); $s = preg_replace("~([\s\S]*?)~i", "${c1}u${c2}\$1${c1}/u${c2}", $s); $s = preg_replace("~([\s\S]*?)~i", "${c1}s${c2}\$1${c1}/s${c2}", $s); - $s = str_replace(array($c1, $c2), array('<','>'), htmlspecialchars($s)); + $s = htmlspecialchars($s); //escape all elements, except above + $s = str_replace( [$c1, $c2], ['<','>'], $s ); //unhide + $s = nl2br($s); + + // make links from text starting with 'www.' + $s = preg_replace( "/(^|\s|>)(www\.([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|"|<|>|\"|<|>|$)/i" , '$1$2$4' , $s ); + + // make link from text starting with protocol like 'http://' + $s = preg_replace( "/(^|\s|>)([a-z]+:\/\/([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|"|<|>|\"|<|>|$)/i" , '$1$2$4' , $s); + return $s; } diff --git a/src/includes/mytinytodo.js b/src/includes/mytinytodo.js index 3a6852a..2971d29 100644 --- a/src/includes/mytinytodo.js +++ b/src/includes/mytinytodo.js @@ -956,7 +956,7 @@ function loadTasks(opts) function prepareTaskStr(item, noteExp) { return '
  • ' + + ((curList.showNotes && item.note != '') || noteExp ? ' task-expanded' : '') + prepareDomClassOfTags(item.tags_ids) + '">
    ' + prepareTaskBlocks(item) + "
  • \n"; }; _mtt.prepareTaskStr = prepareTaskStr; @@ -975,7 +975,7 @@ function prepareTaskBlocks(item) '
    ' + '
    ' + preparePrio(item.prio,id) + - '' + prepareHtml(item.title) + ' ' + + '' + prepareTaskTitleInlineHtml(item.title) + ' ' + (curList.id == -1 ? ''+ tabLists.get(item.listId).name +'' : '') + prepareTagsStr(item) + ''+item.dateInlineTitle+'' + @@ -984,7 +984,7 @@ function prepareTaskBlocks(item) '
    ' + '
    ' + - '
    '+prepareHtml(item.note)+'
    ' + + '
    ' + prepareTaskNoteInlineHtml(item.note) + '
    ' + '
    '+ ''+_mtt.lang.get('actionNoteSave') + ' | '+_mtt.lang.get('actionNoteCancel')+'' + @@ -996,13 +996,19 @@ function prepareTaskBlocks(item) }; _mtt.prepareTaskBlocks = prepareTaskBlocks; -function prepareHtml(s) +function prepareTaskTitleInlineHtml(s) { - // make URLs clickable - s = s.replace(/(^|\s|>)(www\.([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|"|<|>|\"|<|>|$)/gi, '$1$2$4'); - return s.replace(/(^|\s|>)((?:http|https|ftp):\/\/([\w\#$%&~\/.\-\+;:=,\?\[\]@]+?))(,|\.|:|)?(?=\s|"|<|>|\"|<|>|$)/ig, '$1$2$4'); + // Task title is already escaped on php back-end + return s; +} +_mtt.prepareTaskTitleInlineHtml = prepareTaskTitleInlineHtml; + +function prepareTaskNoteInlineHtml(s) +{ + // Task note is already escaped on php back-end + return s; }; -_mtt.prepareHtml = prepareHtml; +_mtt.prepareTaskNoteInlineHtml = prepareTaskNoteInlineHtml; function preparePrio(prio,id) { @@ -1027,7 +1033,7 @@ function prepareTagsStr(item) }; _mtt.prepareTagsStr = prepareTagsStr; -function prepareTagsClass(ids) +function prepareDomClassOfTags(ids) { if(!ids || ids == '') return ''; var a = ids.split(','); @@ -1037,7 +1043,7 @@ function prepareTagsClass(ids) } return ' '+a.join(' '); }; -_mtt.prepareTagsClass = prepareTagsClass; +_mtt.prepareDomClassOfTags = prepareDomClassOfTags; function prepareDueDate(item) { @@ -1456,7 +1462,7 @@ function saveTaskNote(id) var item = json.list[0]; taskList[id].note = item.note; taskList[id].noteText = item.noteText; - $('#tasknote'+id+'>span').html(prepareHtml(item.note)); + $('#tasknote'+id).html(prepareTaskNoteInlineHtml(item.note)); if(item.note == '') $('#taskrow_'+id).removeClass('task-has-note task-expanded'); else $('#taskrow_'+id).addClass('task-has-note task-expanded'); cancelTaskNote(id);