1use crate::JoplinReaderError;
2
3use regex::{Captures, Regex};
4use std::collections::HashMap;
5use std::fs;
6use std::io::{BufRead, BufReader};
7use std::iter::DoubleEndedIterator;
8use std::path::{Path, PathBuf};
9use std::str::Chars;
10use std::time::SystemTime;
11
12use chrono::NaiveDateTime;
13use percent_encoding::percent_decode_str;
14use sjcl::decrypt_raw;
15use serde;
16use serde::ser::{Serialize, Serializer, SerializeStruct};
17
18const REFRESH_INTERVAL: u64 = 60 * 60 * 12;
20const HEADER_SIZE: u32 = 45;
22
23#[derive(Debug, PartialEq, serde::Serialize)]
26pub enum JoplinItemType {
27 Undefined = 0,
28 Note = 1,
29 Folder = 2,
30 Setting = 3,
31 Resource = 4,
32 Tag = 5,
33 NoteTag = 6,
34 Search = 7,
35 Alarm = 8,
36 MasterKey = 9,
37 ItemChange = 10,
38 NoteResource = 11,
39 ResourceLocalState = 12,
40 Revision = 13,
41 Migration = 14,
42 SmartFilter = 15,
43 Command = 16,
44}
45
46impl From<i32> for JoplinItemType {
47 fn from(v: i32) -> Self {
48 match v {
49 1 => JoplinItemType::Note,
50 2 => JoplinItemType::Folder,
51 3 => JoplinItemType::Setting,
52 4 => JoplinItemType::Resource,
53 5 => JoplinItemType::Tag,
54 6 => JoplinItemType::NoteTag,
55 7 => JoplinItemType::Search,
56 8 => JoplinItemType::Alarm,
57 9 => JoplinItemType::MasterKey,
58 10 => JoplinItemType::ItemChange,
59 11 => JoplinItemType::NoteResource,
60 12 => JoplinItemType::ResourceLocalState,
61 13 => JoplinItemType::Revision,
62 14 => JoplinItemType::Migration,
63 15 => JoplinItemType::SmartFilter,
64 16 => JoplinItemType::Command,
65 _ => JoplinItemType::Undefined,
66 }
67 }
68}
69
70#[derive(Debug)]
74pub struct NoteInfo {
75 path: PathBuf,
76 id: String,
77 type_: JoplinItemType,
78 encryption_applied: bool,
79 parent_id: Option<String>,
80 encryption_key_id: Option<String>,
81 updated_time: Option<NaiveDateTime>,
82 read_time: Option<SystemTime>,
84 content: NoteProperties,
85}
86
87impl Serialize for NoteInfo {
88 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
89 where
90 S: Serializer,
91 {
92 let mut state = serializer.serialize_struct("NoteInfo", 9)?;
93 state.serialize_field("created_time", &self.path)?;
94 state.serialize_field("id", &self.id)?;
95 state.serialize_field("type_", &self.type_)?;
96 state.serialize_field("encryption_applied", &self.encryption_applied)?;
97 state.serialize_field("parent_id", &self.parent_id)?;
98 state.serialize_field("encryption_key_id", &self.encryption_key_id)?;
99 state.serialize_field("updated_time", &self.updated_time.map_or(0, |ut| ut.timestamp()))?;
100 state.serialize_field("read_time", &self.read_time)?;
101 state.serialize_field("content", &self.content)?;
102 state.end()
103 }
104}
105
106#[derive(Debug, Clone)]
111pub struct NoteProperties {
112 title: Option<String>,
113 body: Option<String>,
114 created_time: Option<NaiveDateTime>,
115 altitude: Option<f32>,
116 latitude: Option<f64>,
117 longitude: Option<f64>,
118 author: Option<String>,
119 source_url: Option<String>,
120 is_todo: Option<bool>,
121 todo_due: Option<bool>,
122 todo_completed: Option<bool>,
123 source: Option<String>,
124 source_application: Option<String>,
125 application_data: Option<String>,
126 order: Option<i32>,
127 user_created_time: Option<NaiveDateTime>,
128 user_updated_time: Option<NaiveDateTime>,
129 markup_language: Option<String>,
130 is_shared: Option<bool>,
131}
132impl Default for NoteProperties {
133 fn default() -> Self {
134 Self {
135 title: None,
136 body: None,
137 created_time: None,
138 altitude: None,
139 latitude: None,
140 longitude: None,
141 author: None,
142 source_url: None,
143 is_todo: None,
144 todo_due: None,
145 todo_completed: None,
146 source: None,
147 source_application: None,
148 application_data: None,
149 order: None,
150 user_created_time: None,
151 user_updated_time: None,
152 markup_language: None,
153 is_shared: None,
154 }
155 }
156}
157impl From<HashMap<String, String>> for NoteProperties {
158 fn from(mut kv_store: HashMap<String, String>) -> Self {
159 let mut title: Option<String> = None;
160 let mut body: Option<String> = None;
161 let mut created_time: Option<NaiveDateTime> = None;
162 let mut altitude: Option<f32> = None;
163 let mut latitude: Option<f64> = None;
164 let mut longitude: Option<f64> = None;
165 let mut author: Option<String> = None;
166 let mut source_url: Option<String> = None;
167 let mut is_todo: Option<bool> = None;
168 let mut todo_due: Option<bool> = None;
169 let mut todo_completed: Option<bool> = None;
170 let mut source: Option<String> = None;
171 let mut source_application: Option<String> = None;
172 let mut application_data: Option<String> = None;
173 let mut order: Option<i32> = None;
174 let mut user_created_time: Option<NaiveDateTime> = None;
175 let mut user_updated_time: Option<NaiveDateTime> = None;
176 let mut markup_language: Option<String> = None;
177 let mut is_shared: Option<bool> = None;
178
179 for (k, v) in kv_store.drain() {
180 match k.as_str() {
181 "title" => title = Some(v),
182 "body" => body = Some(v),
183 "created_time" => {
184 created_time = match NaiveDateTime::parse_from_str(&v, "%Y-%m-%dT%H:%M:%S%.fZ")
185 {
186 Ok(ut) => Some(ut),
187 Err(_) => None,
188 }
189 }
190 "altitude" => {
191 altitude = match v.trim().parse::<f32>() {
192 Ok(l) => Some(l),
193 _ => None,
194 }
195 }
196 "latitude" => {
197 latitude = match v.trim().parse::<f64>() {
198 Ok(l) => Some(l),
199 _ => None,
200 }
201 }
202 "longitude" => {
203 longitude = match v.trim().parse::<f64>() {
204 Ok(l) => Some(l),
205 _ => None,
206 }
207 }
208 "author" => author = Some(v),
209 "source_url" => source_url = Some(v),
210 "is_todo" => {
211 is_todo = match v.trim().parse::<i8>() {
212 Ok(b) => Some(b == 1),
213 _ => None,
214 }
215 }
216 "todo_due" => {
217 todo_due = match v.trim().parse::<i8>() {
218 Ok(b) => Some(b == 1),
219 _ => None,
220 }
221 }
222 "todo_completed" => {
223 todo_completed = match v.trim().parse::<i8>() {
224 Ok(b) => Some(b == 1),
225 _ => None,
226 }
227 }
228 "source" => source = Some(v),
229 "source_application" => source_application = Some(v),
230 "application_data" => application_data = Some(v),
231 "order" => {
232 order = match v.trim().parse::<i32>() {
233 Ok(o) => Some(o),
234 _ => None,
235 }
236 }
237 "user_created_time" => {
238 user_created_time =
239 match NaiveDateTime::parse_from_str(&v, "%Y-%m-%dT%H:%M:%S%.fZ") {
240 Ok(ut) => Some(ut),
241 Err(_) => None,
242 }
243 }
244 "user_updated_time" => {
245 user_updated_time =
246 match NaiveDateTime::parse_from_str(&v, "%Y-%m-%dT%H:%M:%S%.fZ") {
247 Ok(ut) => Some(ut),
248 Err(_) => None,
249 }
250 }
251 "markup_language" => markup_language = Some(v),
252 "is_shared" => {
253 is_shared = match v.trim().parse::<i8>() {
254 Ok(b) => Some(b == 1),
255 _ => None,
256 }
257 }
258 _ => { }
259 }
260 }
261
262 Self {
263 title,
264 body,
265 created_time,
266 altitude,
267 latitude,
268 longitude,
269 author,
270 source_url,
271 is_todo,
272 todo_due,
273 todo_completed,
274 source,
275 source_application,
276 application_data,
277 order,
278 user_created_time,
279 user_updated_time,
280 markup_language,
281 is_shared,
282 }
283 }
284}
285
286impl Serialize for NoteProperties {
287 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
288 where
289 S: Serializer,
290 {
291 let mut state = serializer.serialize_struct("NoteProperties", 19)?;
292 state.serialize_field("title", &self.title.as_ref().unwrap())?;
293 state.serialize_field("body", &self.body.as_ref().unwrap())?;
294 state.serialize_field("created_time", &self.created_time.as_ref().unwrap().timestamp())?;
295 state.serialize_field("altitude", &self.altitude.as_ref().unwrap())?;
296 state.serialize_field("latitude", &self.latitude.as_ref().unwrap())?;
297 state.serialize_field("longitude", &self.longitude.as_ref().unwrap())?;
298 state.serialize_field("author", &self.author.as_ref().unwrap())?;
299 state.serialize_field("source_url", &self.source_url.as_ref().unwrap())?;
300 state.serialize_field("is_todo", &self.is_todo.as_ref().unwrap())?;
301 state.serialize_field("todo_due", &self.todo_due.as_ref().unwrap())?;
302 state.serialize_field("todo_completed", &self.todo_completed.as_ref().unwrap())?;
303 state.serialize_field("source", &self.source.as_ref().unwrap())?;
304 state.serialize_field("source_application", &self.source_application.as_ref().unwrap())?;
305 state.serialize_field("application_data", &self.application_data.as_ref().unwrap())?;
306 state.serialize_field("order", &self.order.as_ref().unwrap())?;
307 state.serialize_field("user_created_time", &self.user_created_time.as_ref().unwrap().timestamp())?;
308 state.serialize_field("user_updated_time", &self.user_updated_time.as_ref().unwrap().timestamp())?;
309 state.serialize_field("markup_language", &self.markup_language.as_ref().unwrap())?;
310 state.serialize_field("is_shared", &self.is_shared.as_ref().unwrap())?;
311 state.end()
312 }
313}
314
315#[derive(Debug)]
317struct JoplinEncryptionHeader {
318 version: u8,
319 length: u32,
320 encryption_method: JoplinEncryptionMethod,
321 master_key_id: String,
322}
323
324#[derive(Debug, PartialEq)]
329pub enum JoplinEncryptionMethod {
330 MethodUndefined = 0x0,
331 MethodSjcl = 0x1,
332 MethodSjcl2 = 0x2,
333 MethodSjcl3 = 0x3,
334 MethodSjcl4 = 0x4,
335 MethodSjcl1a = 0x5,
336}
337
338impl From<u8> for JoplinEncryptionMethod {
339 fn from(v: u8) -> Self {
340 match v {
341 0x1 => JoplinEncryptionMethod::MethodSjcl,
342 0x2 => JoplinEncryptionMethod::MethodSjcl2,
343 0x3 => JoplinEncryptionMethod::MethodSjcl3,
344 0x4 => JoplinEncryptionMethod::MethodSjcl4,
345 0x5 => JoplinEncryptionMethod::MethodSjcl1a,
346 _ => JoplinEncryptionMethod::MethodUndefined,
347 }
348 }
349}
350
351impl NoteInfo {
352 fn parse_encrypted_file<R: BufRead>(
356 reader: &mut R,
357 ) -> Result<HashMap<String, String>, JoplinReaderError> {
358 let mut kv_store: HashMap<String, String> = HashMap::new();
359 for line in reader.lines() {
360 let line = match line {
361 Ok(line) => line,
362 Err(_) => {
363 return Err(JoplinReaderError::FileReadError {
364 message: "Failed to read file".to_string(),
365 })
366 }
367 };
368
369 let mut iter = line.splitn(2, ":");
370 let key = iter.next();
371 let value = iter.next();
372 if let (Some(key), Some(value)) = (key, value) {
373 kv_store.insert(
375 key.to_string().trim().to_string(),
376 value.to_string().trim().to_string(),
377 );
378 }
379 }
380
381 Ok(kv_store)
382 }
383
384 fn deserialize(
392 text: impl DoubleEndedIterator<Item = impl AsRef<str>>,
393 ) -> Result<HashMap<String, String>, JoplinReaderError> {
394 let mut kv_store: HashMap<String, String> = HashMap::new();
395 let mut body: Vec<String> = Vec::new();
396
397 enum ReadingState {
398 Props,
399 Body,
400 }
401 let mut state: ReadingState = ReadingState::Props;
402 for line in text.rev() {
405 let line = line.as_ref().trim().to_string();
406 match state {
407 ReadingState::Props => {
408 if line.is_empty() {
409 state = ReadingState::Body;
410 continue;
411 }
412
413 let mut iter = line.splitn(2, ":");
414 let key = iter.next();
415 let value = iter.next();
416 if let (Some(key), Some(value)) = (key, value) {
417 kv_store.insert(
419 key.to_string().trim().to_string(),
420 value.to_string().trim().to_string(),
421 );
422 } else {
423 return Err(JoplinReaderError::InvalidFormat {
424 message: "Invalid property format".to_string(),
425 });
426 }
427 }
428 ReadingState::Body => {
429 body.insert(0, line);
431 }
432 }
433 }
434
435 let type_ = match kv_store.get(&"type_".to_string()) {
436 Some(t) => match t.parse::<i32>() {
437 Ok(t) => JoplinItemType::from(t),
438 Err(_) => {
439 return Err(JoplinReaderError::InvalidFormat {
440 message: "Missing required property: `type_`".to_string(),
441 });
442 }
443 },
444 None => {
445 return Err(JoplinReaderError::InvalidFormat {
446 message: "Missing required property: `type_`".to_string(),
447 });
448 }
449 };
450
451 if !body.is_empty() && body.len() >= 2 {
452 kv_store.insert("title".to_string(), body.remove(0));
453 body.remove(0); }
455 if type_ == JoplinItemType::Note {
456 kv_store.insert("body".to_string(), body.join("\n"));
457 }
458
459 Ok(kv_store)
460 }
461
462 pub fn new(note_path: &Path) -> Result<NoteInfo, JoplinReaderError> {
464 let file = match fs::File::open(note_path) {
465 Ok(file) => file,
466 Err(_) => {
467 return Err(JoplinReaderError::FileReadError {
468 message: "Failed to open file".to_string(),
469 })
470 }
471 };
472 let reader = BufReader::new(file);
473
474 let mut id: Option<String> = None;
475 let mut parent_id: Option<String> = None;
476 let mut type_: Option<JoplinItemType> = None;
477 let mut encryption_cipher_text: Option<String> = None;
478 let mut encryption_applied: Option<i8> = None;
479 let mut updated_time: Option<NaiveDateTime> = None;
480
481 for line in reader.lines() {
482 let line = match line {
483 Ok(line) => line,
484 Err(_) => {
485 return Err(JoplinReaderError::FileReadError {
486 message: "Failed to read file".to_string(),
487 })
488 }
489 };
490 let mut iter = line.splitn(2, ":");
491 let key = iter.next();
492 let value = iter.next();
493 if let (Some(key), Some(value)) = (key, value) {
494 match key {
495 "id" => id = Some(value.to_string().trim().to_string()),
496 "parent_id" => parent_id = Some(value.to_string().trim().to_string()),
497 "type_" => {
498 if let Ok(t) = value.to_string().trim().parse::<i32>() {
499 type_ = Some(JoplinItemType::from(t))
500 } else {
501 return Err(JoplinReaderError::FileReadError {
502 message: "Invalid value specified for `type_`".to_string(),
503 });
504 }
505 }
506 "encryption_applied" => {
507 if let Ok(ea) = value.to_string().trim().parse::<i8>() {
508 encryption_applied = Some(ea)
509 } else {
510 return Err(JoplinReaderError::FileReadError {
511 message: "Invalid value specified for `encryption_applied`"
512 .to_string(),
513 });
514 }
515 }
516 "encryption_cipher_text" => {
517 encryption_cipher_text = Some(value.to_string().trim().to_string())
518 }
519 "updated_time" => {
520 let ut = value.to_string().trim().to_string();
521 updated_time =
522 match NaiveDateTime::parse_from_str(&ut, "%Y-%m-%dT%H:%M:%S%.fZ") {
523 Ok(ut) => Some(ut),
524 Err(_) => None,
525 }
526 }
527 _ => { }
528 };
529 }
530 }
531
532 if let None = id {
534 return Err(JoplinReaderError::FileReadError {
535 message: "No `id` specified in note".to_string(),
536 });
537 }
538 if let None = encryption_applied {
539 return Err(JoplinReaderError::FileReadError {
540 message: "No `encryption_applied` attribute specified in note".to_string(),
541 });
542 }
543 let encryption_applied = encryption_applied.unwrap();
544 let encryption_applied = match encryption_applied {
545 1 => true,
546 _ => false,
547 };
548 let encryption_key_id = match encryption_applied {
549 true => match NoteInfo::parse_encrypted_header(
550 encryption_cipher_text.clone().unwrap().chars(),
551 ) {
552 Ok(header) => Some(header.master_key_id),
553 Err(_) => {
554 return Err(JoplinReaderError::FileReadError {
555 message: "Failed to read the encryption header".to_string(),
556 });
557 }
558 },
559 _ => None,
560 };
561
562 Ok(NoteInfo {
563 path: note_path.to_path_buf(),
564 id: id.unwrap(),
565 type_: type_.unwrap(),
566 encryption_applied,
567 parent_id,
568 encryption_key_id,
569 updated_time,
570 read_time: None,
571 content: NoteProperties::default(),
572 })
573 }
574
575 pub fn get_id(&self) -> &str {
576 &self.id
577 }
578
579 pub fn is_encrypted(&self) -> bool {
580 self.encryption_applied
581 }
582
583 pub fn get_type_(&self) -> &JoplinItemType {
584 &self.type_
585 }
586
587 pub fn get_parent_id(&self) -> Option<&str> {
588 match &self.parent_id {
589 Some(parent_id) => Some(&parent_id),
590 None => None,
591 }
592 }
593
594 pub fn get_encryption_key_id(&self) -> Option<&str> {
595 match &self.encryption_key_id {
596 Some(encryption_key_id) => Some(&encryption_key_id),
597 None => None,
598 }
599 }
600
601 fn parse_encrypted_header(
604 mut chars: Chars<'_>,
605 ) -> Result<JoplinEncryptionHeader, JoplinReaderError> {
606 let mut identifier = String::from("");
608 for _ in 0..3 {
609 if let Some(v) = chars.next() {
610 identifier.push(v);
611 }
612 }
613 if identifier.is_empty() || identifier.len() != 3 {
614 return Err(JoplinReaderError::DecryptionError {
615 message: "Header has invalid size".to_string(),
616 });
617 }
618 if identifier != "JED" {
619 return Err(JoplinReaderError::DecryptionError {
620 message: "Identifier is not 'JED'".to_string(),
621 });
622 }
623 let mut version = String::from("");
625 for _ in 0..2 {
626 if let Some(v) = chars.next() {
627 version.push(v);
628 }
629 }
630 if version.is_empty() || version.len() != 2 {
631 return Err(JoplinReaderError::DecryptionError {
632 message: "Header has invalid size".to_string(),
633 });
634 }
635 let version = match u8::from_str_radix(&version, 16) {
636 Ok(v) => v,
637 Err(_) => {
638 return Err(JoplinReaderError::DecryptionError {
639 message: "Version is not a number".to_string(),
640 });
641 }
642 };
643 if version != 1 {
644 return Err(JoplinReaderError::DecryptionError {
645 message: "Invalid version. Needs to be '01'".to_string(),
646 });
647 }
648 let mut length = String::from("");
650 for _ in 0..6 {
651 if let Some(v) = chars.next() {
652 length.push(v);
653 }
654 }
655 if length.is_empty() || length.len() != 6 {
656 return Err(JoplinReaderError::DecryptionError {
657 message: "Header has invalid size".to_string(),
658 });
659 }
660 let length = match u32::from_str_radix(&length, 16) {
661 Ok(v) => v,
662 Err(_) => {
663 return Err(JoplinReaderError::DecryptionError {
664 message: "Length is not a number".to_string(),
665 });
666 }
667 };
668 if length != 34 {
669 return Err(JoplinReaderError::DecryptionError {
670 message: "Expected length 34: Method + master key id".to_string(),
671 });
672 }
673 let mut encryption_method = String::from("");
675 for _ in 0..2 {
676 if let Some(v) = chars.next() {
677 encryption_method.push(v);
678 }
679 }
680 if encryption_method.is_empty() || encryption_method.len() != 2 {
681 return Err(JoplinReaderError::DecryptionError {
682 message: "Header has invalid size".to_string(),
683 });
684 }
685 let encryption_method = match u8::from_str_radix(&encryption_method, 16) {
686 Ok(v) => JoplinEncryptionMethod::from(v),
687 Err(_) => {
688 return Err(JoplinReaderError::DecryptionError {
689 message: "Encryption Method is not a number".to_string(),
690 });
691 }
692 };
693 if encryption_method == JoplinEncryptionMethod::MethodUndefined {
694 return Err(JoplinReaderError::DecryptionError {
695 message: "Unknown decryption method".to_string(),
696 });
697 }
698 let mut master_key_id = String::from("");
700 for _ in 0..32 {
701 if let Some(v) = chars.next() {
702 master_key_id.push(v);
703 }
704 }
705 if master_key_id.is_empty() || master_key_id.len() != 32 {
706 return Err(JoplinReaderError::DecryptionError {
707 message: "Header has invalid size".to_string(),
708 });
709 }
710
711 Ok(JoplinEncryptionHeader {
712 version,
713 length,
714 encryption_method,
715 master_key_id,
716 })
717 }
718
719 fn clean_encoded_ascii(text: String) -> String {
720 let re = Regex::new(r"%([0-9a-fA-F]{2})").unwrap();
721
722 let text = re.replace_all(&text, |caps: &Captures| {
723 let value = caps[0].strip_prefix("%").unwrap();
724 let value = u8::from_str_radix(value, 16).unwrap();
725 let value = value as char;
726 value.to_string()
727 });
728
729 text.to_string()
730 }
731
732 fn clean_encoded_unicode(text: String) -> String {
733 let re = Regex::new(r"%u([0-9a-fA-F]{4})").unwrap();
734
735 let text = re.replace_all(&text, |_caps: &Captures| {
736 "".to_string()
743 });
744
745 text.to_string()
746 }
747
748 fn decrypt(mut chars: Chars<'_>, encryption_key: &str) -> Result<String, JoplinReaderError> {
751 let mut _chunks_read: u32 = 0;
752 let mut _bytes_read: u32 = 0;
753 let mut body = String::from("");
754 loop {
755 let mut length = String::from("");
756 for _ in 0..6 {
757 if let Some(v) = chars.next() {
758 length.push(v);
759 }
760 }
761 if length.is_empty() || length.len() != 6 {
762 break;
763 }
764 let length = match u32::from_str_radix(&length, 16) {
765 Ok(v) => v,
766 Err(_) => {
767 return Err(JoplinReaderError::DecryptionError {
768 message: "Length is not a number".to_string(),
769 });
770 }
771 };
772
773 let mut data = String::from("");
774 for _ in 0..length {
775 if let Some(v) = chars.next() {
776 data.push(v);
777 }
778 }
779 if data.is_empty() || data.len() != length as usize {
780 return Err(JoplinReaderError::UnexpectedEndOfNote);
781 }
782 match decrypt_raw(data, encryption_key.to_string()) {
783 Ok(data) => {
784 let data = match String::from_utf8(data) {
785 Ok(data) => data,
786 Err(_) => {
787 return Err(JoplinReaderError::DecryptionError {
788 message: "Message did not contain valid ascii".to_string(),
789 })
790 }
791 };
792 let data = NoteInfo::clean_encoded_ascii(data);
793 let data = NoteInfo::clean_encoded_unicode(data);
794 body.push_str(&data)
795 }
796 Err(_) => {
797 return Err(JoplinReaderError::DecryptionError {
798 message: "Error decrypting".to_string(),
799 })
800 }
801 };
802
803 _bytes_read += length;
804 _chunks_read += 1;
805 }
806 let body = percent_decode_str(&body).decode_utf8_lossy();
807 Ok(body.to_string())
808 }
809
810 fn read_content(&mut self, encryption_key: Option<&str>) -> Result<(), JoplinReaderError> {
812 let content = match self.is_encrypted() {
813 true => self.read_decrypted(encryption_key),
814 false => self.read_unencrypted(),
815 };
816
817 match content {
818 Ok(content) => {
819 self.content = NoteProperties::from(content);
820 Ok(())
821 }
822 Err(e) => Err(e),
823 }
824 }
825
826 fn read_unencrypted(&self) -> Result<HashMap<String, String>, JoplinReaderError> {
829 let file = match fs::File::open(self.path.clone()) {
830 Ok(file) => file,
831 Err(_) => {
832 return Err(JoplinReaderError::FileReadError {
833 message: "Failed to open file".to_string(),
834 })
835 }
836 };
837 let reader = BufReader::new(file);
838 let mut text: Vec<String> = Vec::new();
839 for line in reader.lines() {
841 let line = line.unwrap();
842 text.insert(0, line);
843 }
844
845 NoteInfo::deserialize(text.iter())
846 }
847
848 fn read_decrypted(
851 &self,
852 encryption_key: Option<&str>,
853 ) -> Result<HashMap<String, String>, JoplinReaderError> {
854 let encryption_key = match encryption_key {
855 Some(ek) => ek,
856 _ => {
857 return Err(JoplinReaderError::NoEncryptionKey { key: format!("{:?}", encryption_key)});
858 }
859 };
860
861 let file = match fs::File::open(&self.path) {
862 Ok(file) => file,
863 Err(_) => {
864 return Err(JoplinReaderError::FileReadError {
865 message: "Failed to open file".to_string(),
866 })
867 }
868 };
869 let mut reader = BufReader::new(file);
870 let content = match NoteInfo::parse_encrypted_file(&mut reader) {
871 Ok(content) => content,
872 Err(e) => return Err(e),
873 };
874
875 if let Some(text) = content.get(&"encryption_cipher_text".to_string()) {
876 if !text.is_ascii() {
877 return Err(JoplinReaderError::DecryptionError {
878 message: "Encrypted text is not ascii".to_string(),
879 });
880 }
881 let mut chars = text.chars();
882 for _ in 0..HEADER_SIZE {
884 chars.next();
885 }
886 let plaintext = match NoteInfo::decrypt(chars, encryption_key) {
887 Ok(plaintext) => plaintext,
888 Err(_e) => {
889 println!("{:?}", _e);
890 return Err(JoplinReaderError::DecryptionError {
891 message: "Failed to decrypt SJCL chunks".to_string(),
892 });
893 }
894 };
895
896 NoteInfo::deserialize(plaintext.lines())
897 } else {
898 Err(JoplinReaderError::NoEncryptionText)
899 }
900 }
901
902 pub fn read(&mut self, encryption_key: Option<&str>) -> Result<&str, JoplinReaderError> {
906 let reading = match self.read_time {
907 None => self.read_content(encryption_key),
908 Some(t) => {
909 let since_last_refresh = SystemTime::now()
910 .duration_since(t)
911 .expect("Time went backwards!")
912 .as_secs();
913 if since_last_refresh >= REFRESH_INTERVAL {
914 self.read_content(encryption_key)
915 } else {
916 Ok(())
917 }
918 }
919 };
920
921 match reading {
922 Ok(_) => match &self.content.body {
923 Some(body) => Ok(body),
924 None => Err(JoplinReaderError::NoText),
925 },
926 Err(e) => Err(e),
927 }
928 }
929}