utils.lisp 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. (in-package :cl-user)
  2. (defpackage #:timeliner.utils
  3. (:use :cl :cl-mongo :sqlite)
  4. (:export
  5. #:ts->ms
  6. #:ms->ts
  7. #:$between
  8. #:make-keyword
  9. #:starts-with
  10. #:aget
  11. #:doc->plist
  12. #:load-chrome-cookie-jar
  13. #:today))
  14. (in-package :timeliner.utils)
  15. (defun ts->ms (ts)
  16. (+ (* 1000 (local-time:timestamp-to-unix ts))
  17. (floor (local-time:nsec-of ts) 1000000)))
  18. (defun ms->ts (ms)
  19. (multiple-value-bind (unix msec) (floor ms 1000)
  20. (local-time:unix-to-timestamp unix :nsec (* msec 1000000))))
  21. (defun today ()
  22. (local-time:adjust-timestamp (local-time:now)
  23. (set :hour 0)
  24. (set :minute 0)
  25. (set :sec 0)
  26. (set :nsec 0)))
  27. (defgeneric $between (a from to)
  28. (:documentation "cl-mongo between query"))
  29. (defmethod $between (a from to)
  30. (kv ($>= a from) ($< a to)))
  31. (defmethod $between (a (from local-time:timestamp) (to local-time:timestamp))
  32. ($between a
  33. (cl-mongo::make-bson-time (ts->ms from))
  34. (cl-mongo::make-bson-time (ts->ms to))))
  35. (defun make-keyword (name) (values (intern (string-upcase name) "KEYWORD")))
  36. (defun starts-with (str with)
  37. (string= (subseq str 0 (min (length str)
  38. (length with)))
  39. with))
  40. (defmacro aget (key alist)
  41. `(cdr (assoc ,key ,alist :test #'string=)))
  42. (defvar *decode-doc-to* :plist "Document object type")
  43. (defgeneric decode-element (element)
  44. (:documentation "Convert cl-mongo element to preferred type"))
  45. (defmethod decode-element (element)
  46. element)
  47. (defmethod decode-element ((element document))
  48. (case *decode-doc-to*
  49. (:plist (doc->plist element))))
  50. (defmethod decode-element ((element cl-mongo::bson-time))
  51. (ms->ts (cl-mongo::raw element)))
  52. (defun doc->plist (doc)
  53. (let ((*decode-doc-to* :plist)
  54. result)
  55. (loop for key in (get-keys doc)
  56. do (setf (getf result (make-keyword key))
  57. (decode-element (get-element key doc))))
  58. result))
  59. (defmethod yason:encode ((object document) &optional (stream *standard-output*))
  60. (let ((hash (cl-mongo::elements object)))
  61. (unless (cl-mongo::_local object)
  62. (setf (gethash "_id" hash)
  63. (crypto:byte-array-to-hex-string (doc-id object))))
  64. (yason:encode hash stream)))
  65. (defmethod yason:encode ((object cl-mongo::bson-time) &optional (stream *standard-output*))
  66. (yason:encode (cl-mongo::raw object) stream))
  67. ;; Google Chrome Cookies loading
  68. (defvar *chrome-cookie-password*
  69. (crypto:ascii-string-to-byte-array "peanuts")
  70. "Password for PBKDF2")
  71. (defvar *chrome-cookie-salt*
  72. (crypto:ascii-string-to-byte-array "saltysalt")
  73. "Salt for PBKDF2")
  74. (defvar *chrome-cookie-iterations* 1 "PBKDF2 iteration count")
  75. (defvar *chrome-cookie-key-length* 16 "PBKDF2 key length")
  76. (defun chrome-make-cookie-cipher ()
  77. (crypto:make-cipher 'crypto:aes :mode 'crypto:cbc
  78. :key (crypto:derive-key
  79. (crypto:make-kdf 'crypto:pbkdf2 :digest 'crypto:sha1)
  80. *chrome-cookie-password*
  81. *chrome-cookie-salt*
  82. *chrome-cookie-iterations*
  83. *chrome-cookie-key-length*)
  84. :initialization-vector (crypto:ascii-string-to-byte-array
  85. " ")))
  86. (defvar *chrome-cookie-file*
  87. (merge-pathnames #P".config/google-chrome/Default/Cookies"
  88. (user-homedir-pathname)))
  89. (define-condition bad-encoding-error (simple-error) ())
  90. (defun chrome-cookie-decode (value)
  91. (when (mismatch (subseq value 0 3)
  92. (mapcar #'char-code (coerce "v10" 'list)))
  93. (error 'bad-encoding-verion))
  94. (crypto:decrypt-in-place (chrome-make-cookie-cipher) value :start 3)
  95. (coerce (mapcar #'code-char (subseq (coerce value 'list)
  96. 3 (- (length value)
  97. (elt value (1- (length value)))))) 'string))
  98. (defun load-chrome-cookie-jar (domain)
  99. (sqlite:with-open-database (db *chrome-cookie-file*)
  100. (make-instance 'drakma:cookie-jar
  101. :cookies (loop
  102. for (name host-key value path expires secure-p http-only-p encrypted)
  103. in (sqlite::execute-to-list
  104. db (format nil "select name, host_key, value, path, expires_utc, secure, httponly, encrypted_value from cookies where host_key like '%~A%'" domain))
  105. collect (make-instance 'drakma:cookie
  106. :name name
  107. :domain host-key
  108. :value (if (string= value "")
  109. (chrome-cookie-decode encrypted)
  110. value)
  111. :path path
  112. :expires (if (equal expires 0) nil (floor expires 1000))
  113. :securep (equal secure-p 1)
  114. :http-only-p (equal http-only-p 1))))))
  115. ;;; parenscript macros
  116. (defpsmacro ! (&rest method-calls)
  117. `(chain ,@method-calls))