diff --git a/controller/notesapicontroller.php b/controller/notesapicontroller.php index 4f55b3f93..52cd8c464 100644 --- a/controller/notesapicontroller.php +++ b/controller/notesapicontroller.php @@ -64,6 +64,17 @@ private function excludeFields(Note $note, array $exclude) { } + private function excludeIfNotModified(Note $note, array $etags) { + $etag = $note->getEtag(); + if($etag!==null && in_array($etag, $etags)) { + $vars = get_object_vars($note); + unset($vars['id']); + $this->excludeFields($note, array_keys($vars)); + } + return $note; + } + + /** * @NoAdminRequired * @CORS @@ -74,11 +85,14 @@ private function excludeFields(Note $note, array $exclude) { */ public function index($exclude='') { $exclude = explode(',', $exclude); + $etags = array_key_exists('HTTP_X_NOTES_ETAGS', $_SERVER) ? $_SERVER['HTTP_X_NOTES_ETAGS'] : ''; + $etags = str_split($etags, 20); $notes = $this->service->getAll($this->userId); foreach ($notes as $note) { $note = $this->excludeFields($note, $exclude); + $note = $this->excludeIfNotModified($note, $etags); } - return new DataResponse($notes); + return (new DataResponse($notes))->addHeader('Vary', 'X-Notes-Etags'); } diff --git a/db/note.php b/db/note.php index f277ea061..f93c67cf6 100644 --- a/db/note.php +++ b/db/note.php @@ -19,6 +19,8 @@ * Class Note * @method integer getId() * @method void setId(integer $value) + * @method string getEtag() + * @method void setEtag(string $value) * @method integer getModified() * @method void setModified(integer $value) * @method string getTitle() @@ -33,6 +35,7 @@ */ class Note extends Entity { + public $etag; public $modified; public $title; public $category; @@ -60,8 +63,34 @@ public static function fromFile(File $file, Folder $notesFolder, $tags=[]){ $note->setFavorite(true); //unset($tags[array_search(\OC\Tags::TAG_FAVORITE, $tags)]); } + $note->updateETag(); $note->resetUpdatedFields(); return $note; } + private function updateETag() { + $etag = ''; + // collect all relevant attributes + $data = ''; + foreach(get_object_vars($this) as $key => $val) { + if($key!=='etag') { + $data .= $val; + } + } + // create binary checksum + $md5 = md5($data, true); + // binary-to-text using a base85 derivate: + // - only 25% larger than binary data + // - replace problematic characters by not-used ones (no escaping necessary in JSON or HTTP-header) + // - the result has always the same length (20 characters for 16byte md5-checksum) + foreach(unpack('N*', $md5) as $chunk) { + for ($a = 0; $a < 5; $a++) { + $b = intval($chunk / (pow(85,4 - $a))); + $chr = str_replace(array('\\', '/', '<', '>'), array('z', '|', '{', '}'), chr($b + 35)); + $etag .= $chr; + $chunk -= $b * pow(85,4 - $a); + } + } + $this->setEtag($etag); + } } diff --git a/tests/unit/controller/NotesApiControllerTest.php b/tests/unit/controller/NotesApiControllerTest.php index 3ce8cab6d..3f3feeab9 100644 --- a/tests/unit/controller/NotesApiControllerTest.php +++ b/tests/unit/controller/NotesApiControllerTest.php @@ -87,12 +87,14 @@ public function testGetAllHide(){ $this->assertEquals(json_encode([ [ + 'etag' => null, 'modified' => 123, 'category' => null, 'favorite' => false, 'id' => 3, ], [ + 'etag' => null, 'modified' => 111, 'category' => null, 'favorite' => false, @@ -139,6 +141,7 @@ public function testGetHide(){ $response = $this->controller->get(3, 'title,content'); $this->assertEquals(json_encode([ + 'etag' => null, 'modified' => 123, 'category' => null, 'favorite' => false,