tilia-dav 3.1.0.pre.alpha2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (499) hide show
  1. checksums.yaml +7 -0
  2. data/.database.travis.yml +6 -0
  3. data/.gitignore +25 -0
  4. data/.rubocop.yml +35 -0
  5. data/.simplecov +4 -0
  6. data/.travis.yml +10 -0
  7. data/CHANGELOG.sabre.md +2084 -0
  8. data/CONTRIBUTING.md +25 -0
  9. data/Gemfile +25 -0
  10. data/Gemfile.lock +103 -0
  11. data/LICENSE +27 -0
  12. data/LICENSE.sabre +27 -0
  13. data/README.md +40 -0
  14. data/Rakefile +18 -0
  15. data/database.sample.yml +6 -0
  16. data/examples/minimal.rb +25 -0
  17. data/lib/tilia/cal_dav.rb +27 -0
  18. data/lib/tilia/cal_dav/backend.rb +17 -0
  19. data/lib/tilia/cal_dav/backend/abstract_backend.rb +194 -0
  20. data/lib/tilia/cal_dav/backend/backend_interface.rb +250 -0
  21. data/lib/tilia/cal_dav/backend/notification_support.rb +38 -0
  22. data/lib/tilia/cal_dav/backend/scheduling_support.rb +57 -0
  23. data/lib/tilia/cal_dav/backend/sequel.rb +1118 -0
  24. data/lib/tilia/cal_dav/backend/sharing_support.rb +239 -0
  25. data/lib/tilia/cal_dav/backend/subscription_support.rb +79 -0
  26. data/lib/tilia/cal_dav/backend/sync_support.rb +75 -0
  27. data/lib/tilia/cal_dav/calendar.rb +426 -0
  28. data/lib/tilia/cal_dav/calendar_home.rb +335 -0
  29. data/lib/tilia/cal_dav/calendar_object.rb +219 -0
  30. data/lib/tilia/cal_dav/calendar_query_validator.rb +294 -0
  31. data/lib/tilia/cal_dav/calendar_root.rb +57 -0
  32. data/lib/tilia/cal_dav/exception.rb +7 -0
  33. data/lib/tilia/cal_dav/exception/invalid_component_type.rb +21 -0
  34. data/lib/tilia/cal_dav/i_calendar.rb +11 -0
  35. data/lib/tilia/cal_dav/i_calendar_object.rb +13 -0
  36. data/lib/tilia/cal_dav/i_calendar_object_container.rb +32 -0
  37. data/lib/tilia/cal_dav/i_shareable_calendar.rb +40 -0
  38. data/lib/tilia/cal_dav/i_shared_calendar.rb +28 -0
  39. data/lib/tilia/cal_dav/ics_export_plugin.rb +327 -0
  40. data/lib/tilia/cal_dav/notifications.rb +12 -0
  41. data/lib/tilia/cal_dav/notifications/collection.rb +131 -0
  42. data/lib/tilia/cal_dav/notifications/i_collection.rb +17 -0
  43. data/lib/tilia/cal_dav/notifications/i_node.rb +30 -0
  44. data/lib/tilia/cal_dav/notifications/node.rb +142 -0
  45. data/lib/tilia/cal_dav/notifications/plugin.rb +138 -0
  46. data/lib/tilia/cal_dav/plugin.rb +891 -0
  47. data/lib/tilia/cal_dav/principal.rb +12 -0
  48. data/lib/tilia/cal_dav/principal/collection.rb +21 -0
  49. data/lib/tilia/cal_dav/principal/i_proxy_read.rb +13 -0
  50. data/lib/tilia/cal_dav/principal/i_proxy_write.rb +13 -0
  51. data/lib/tilia/cal_dav/principal/proxy_read.rb +127 -0
  52. data/lib/tilia/cal_dav/principal/proxy_write.rb +127 -0
  53. data/lib/tilia/cal_dav/principal/user.rb +96 -0
  54. data/lib/tilia/cal_dav/schedule.rb +14 -0
  55. data/lib/tilia/cal_dav/schedule/i_inbox.rb +12 -0
  56. data/lib/tilia/cal_dav/schedule/i_mip_plugin.rb +156 -0
  57. data/lib/tilia/cal_dav/schedule/i_outbox.rb +12 -0
  58. data/lib/tilia/cal_dav/schedule/i_scheduling_object.rb +10 -0
  59. data/lib/tilia/cal_dav/schedule/inbox.rb +211 -0
  60. data/lib/tilia/cal_dav/schedule/outbox.rb +143 -0
  61. data/lib/tilia/cal_dav/schedule/plugin.rb +851 -0
  62. data/lib/tilia/cal_dav/schedule/scheduling_object.rb +126 -0
  63. data/lib/tilia/cal_dav/shareable_calendar.rb +54 -0
  64. data/lib/tilia/cal_dav/shared_calendar.rb +120 -0
  65. data/lib/tilia/cal_dav/sharing_plugin.rb +359 -0
  66. data/lib/tilia/cal_dav/subscriptions.rb +9 -0
  67. data/lib/tilia/cal_dav/subscriptions/i_subscription.rb +37 -0
  68. data/lib/tilia/cal_dav/subscriptions/plugin.rb +83 -0
  69. data/lib/tilia/cal_dav/subscriptions/subscription.rb +205 -0
  70. data/lib/tilia/cal_dav/xml.rb +10 -0
  71. data/lib/tilia/cal_dav/xml/filter.rb +12 -0
  72. data/lib/tilia/cal_dav/xml/filter/calendar_data.rb +64 -0
  73. data/lib/tilia/cal_dav/xml/filter/comp_filter.rb +79 -0
  74. data/lib/tilia/cal_dav/xml/filter/param_filter.rb +66 -0
  75. data/lib/tilia/cal_dav/xml/filter/prop_filter.rb +80 -0
  76. data/lib/tilia/cal_dav/xml/notification.rb +13 -0
  77. data/lib/tilia/cal_dav/xml/notification/invite.rb +253 -0
  78. data/lib/tilia/cal_dav/xml/notification/invite_reply.rb +167 -0
  79. data/lib/tilia/cal_dav/xml/notification/notification_interface.rb +41 -0
  80. data/lib/tilia/cal_dav/xml/notification/system_status.rb +139 -0
  81. data/lib/tilia/cal_dav/xml/property.rb +15 -0
  82. data/lib/tilia/cal_dav/xml/property/allowed_sharing_modes.rb +64 -0
  83. data/lib/tilia/cal_dav/xml/property/email_address_set.rb +60 -0
  84. data/lib/tilia/cal_dav/xml/property/invite.rb +207 -0
  85. data/lib/tilia/cal_dav/xml/property/schedule_calendar_transp.rb +108 -0
  86. data/lib/tilia/cal_dav/xml/property/supported_calendar_component_set.rb +100 -0
  87. data/lib/tilia/cal_dav/xml/property/supported_calendar_data.rb +50 -0
  88. data/lib/tilia/cal_dav/xml/property/supported_collation_set.rb +47 -0
  89. data/lib/tilia/cal_dav/xml/request.rb +14 -0
  90. data/lib/tilia/cal_dav/xml/request/calendar_multi_get_report.rb +99 -0
  91. data/lib/tilia/cal_dav/xml/request/calendar_query_report.rb +112 -0
  92. data/lib/tilia/cal_dav/xml/request/free_busy_query_report.rb +70 -0
  93. data/lib/tilia/cal_dav/xml/request/invite_reply.rb +110 -0
  94. data/lib/tilia/cal_dav/xml/request/mk_calendar.rb +67 -0
  95. data/lib/tilia/cal_dav/xml/request/share.rb +93 -0
  96. data/lib/tilia/card_dav.rb +17 -0
  97. data/lib/tilia/card_dav/address_book.rb +338 -0
  98. data/lib/tilia/card_dav/address_book_home.rb +192 -0
  99. data/lib/tilia/card_dav/address_book_root.rb +58 -0
  100. data/lib/tilia/card_dav/backend.rb +12 -0
  101. data/lib/tilia/card_dav/backend/abstract_backend.rb +30 -0
  102. data/lib/tilia/card_dav/backend/backend_interface.rb +175 -0
  103. data/lib/tilia/card_dav/backend/sequel.rb +476 -0
  104. data/lib/tilia/card_dav/backend/sync_support.rb +80 -0
  105. data/lib/tilia/card_dav/card.rb +193 -0
  106. data/lib/tilia/card_dav/i_address_book.rb +10 -0
  107. data/lib/tilia/card_dav/i_card.rb +11 -0
  108. data/lib/tilia/card_dav/i_directory.rb +14 -0
  109. data/lib/tilia/card_dav/plugin.rb +724 -0
  110. data/lib/tilia/card_dav/vcf_export_plugin.rb +122 -0
  111. data/lib/tilia/card_dav/xml.rb +9 -0
  112. data/lib/tilia/card_dav/xml/filter.rb +11 -0
  113. data/lib/tilia/card_dav/xml/filter/address_data.rb +50 -0
  114. data/lib/tilia/card_dav/xml/filter/param_filter.rb +71 -0
  115. data/lib/tilia/card_dav/xml/filter/prop_filter.rb +77 -0
  116. data/lib/tilia/card_dav/xml/property.rb +10 -0
  117. data/lib/tilia/card_dav/xml/property/supported_address_data.rb +67 -0
  118. data/lib/tilia/card_dav/xml/property/supported_collation_set.rb +38 -0
  119. data/lib/tilia/card_dav/xml/request.rb +10 -0
  120. data/lib/tilia/card_dav/xml/request/address_book_multi_get_report.rb +91 -0
  121. data/lib/tilia/card_dav/xml/request/address_book_query_report.rb +156 -0
  122. data/lib/tilia/dav.rb +94 -0
  123. data/lib/tilia/dav/auth.rb +8 -0
  124. data/lib/tilia/dav/auth/backend.rb +15 -0
  125. data/lib/tilia/dav/auth/backend/abstract_basic.rb +119 -0
  126. data/lib/tilia/dav/auth/backend/abstract_digest.rb +132 -0
  127. data/lib/tilia/dav/auth/backend/apache.rb +85 -0
  128. data/lib/tilia/dav/auth/backend/backend_interface.rb +61 -0
  129. data/lib/tilia/dav/auth/backend/basic_call_back.rb +46 -0
  130. data/lib/tilia/dav/auth/backend/file.rb +61 -0
  131. data/lib/tilia/dav/auth/backend/sequel.rb +46 -0
  132. data/lib/tilia/dav/auth/plugin.rb +157 -0
  133. data/lib/tilia/dav/browser.rb +12 -0
  134. data/lib/tilia/dav/browser/assets/favicon.ico +0 -0
  135. data/lib/tilia/dav/browser/assets/openiconic/ICON-LICENSE +21 -0
  136. data/lib/tilia/dav/browser/assets/openiconic/open-iconic.css +510 -0
  137. data/lib/tilia/dav/browser/assets/openiconic/open-iconic.eot +0 -0
  138. data/lib/tilia/dav/browser/assets/openiconic/open-iconic.otf +0 -0
  139. data/lib/tilia/dav/browser/assets/openiconic/open-iconic.svg +543 -0
  140. data/lib/tilia/dav/browser/assets/openiconic/open-iconic.ttf +0 -0
  141. data/lib/tilia/dav/browser/assets/openiconic/open-iconic.woff +0 -0
  142. data/lib/tilia/dav/browser/assets/sabredav.css +228 -0
  143. data/lib/tilia/dav/browser/assets/sabredav.png +0 -0
  144. data/lib/tilia/dav/browser/guess_content_type.rb +80 -0
  145. data/lib/tilia/dav/browser/html_output.rb +27 -0
  146. data/lib/tilia/dav/browser/html_output_helper.rb +86 -0
  147. data/lib/tilia/dav/browser/map_get_to_prop_find.rb +41 -0
  148. data/lib/tilia/dav/browser/plugin.rb +693 -0
  149. data/lib/tilia/dav/browser/prop_find_all.rb +95 -0
  150. data/lib/tilia/dav/client.rb +341 -0
  151. data/lib/tilia/dav/collection.rb +79 -0
  152. data/lib/tilia/dav/core_plugin.rb +824 -0
  153. data/lib/tilia/dav/exception.rb +59 -0
  154. data/lib/tilia/dav/exception/bad_request.rb +18 -0
  155. data/lib/tilia/dav/exception/conflict.rb +18 -0
  156. data/lib/tilia/dav/exception/conflicting_lock.rb +26 -0
  157. data/lib/tilia/dav/exception/forbidden.rb +18 -0
  158. data/lib/tilia/dav/exception/insufficient_storage.rb +18 -0
  159. data/lib/tilia/dav/exception/invalid_resource_type.rb +23 -0
  160. data/lib/tilia/dav/exception/invalid_sync_token.rb +26 -0
  161. data/lib/tilia/dav/exception/length_required.rb +18 -0
  162. data/lib/tilia/dav/exception/lock_token_matches_request_uri.rb +25 -0
  163. data/lib/tilia/dav/exception/locked.rb +48 -0
  164. data/lib/tilia/dav/exception/method_not_allowed.rb +29 -0
  165. data/lib/tilia/dav/exception/not_authenticated.rb +18 -0
  166. data/lib/tilia/dav/exception/not_found.rb +18 -0
  167. data/lib/tilia/dav/exception/not_implemented.rb +18 -0
  168. data/lib/tilia/dav/exception/payment_required.rb +18 -0
  169. data/lib/tilia/dav/exception/precondition_failed.rb +47 -0
  170. data/lib/tilia/dav/exception/report_not_supported.rb +21 -0
  171. data/lib/tilia/dav/exception/requested_range_not_satisfiable.rb +18 -0
  172. data/lib/tilia/dav/exception/service_unavailable.rb +18 -0
  173. data/lib/tilia/dav/exception/too_many_matches.rb +21 -0
  174. data/lib/tilia/dav/exception/unsupported_media_type.rb +18 -0
  175. data/lib/tilia/dav/file.rb +58 -0
  176. data/lib/tilia/dav/fs.rb +9 -0
  177. data/lib/tilia/dav/fs/directory.rb +119 -0
  178. data/lib/tilia/dav/fs/file.rb +69 -0
  179. data/lib/tilia/dav/fs/node.rb +57 -0
  180. data/lib/tilia/dav/fs_ext.rb +8 -0
  181. data/lib/tilia/dav/fs_ext/directory.rb +175 -0
  182. data/lib/tilia/dav/fs_ext/file.rb +118 -0
  183. data/lib/tilia/dav/i_collection.rb +65 -0
  184. data/lib/tilia/dav/i_extended_collection.rb +36 -0
  185. data/lib/tilia/dav/i_file.rb +70 -0
  186. data/lib/tilia/dav/i_move_target.rb +37 -0
  187. data/lib/tilia/dav/i_multi_get.rb +29 -0
  188. data/lib/tilia/dav/i_node.rb +33 -0
  189. data/lib/tilia/dav/i_properties.rb +39 -0
  190. data/lib/tilia/dav/i_quota.rb +19 -0
  191. data/lib/tilia/dav/locks.rb +9 -0
  192. data/lib/tilia/dav/locks/backend.rb +12 -0
  193. data/lib/tilia/dav/locks/backend/abstract_backend.rb +16 -0
  194. data/lib/tilia/dav/locks/backend/backend_interface.rb +41 -0
  195. data/lib/tilia/dav/locks/backend/file.rb +146 -0
  196. data/lib/tilia/dav/locks/backend/sequel.rb +154 -0
  197. data/lib/tilia/dav/locks/lock_info.rb +60 -0
  198. data/lib/tilia/dav/locks/plugin.rb +467 -0
  199. data/lib/tilia/dav/mk_col.rb +47 -0
  200. data/lib/tilia/dav/mount.rb +7 -0
  201. data/lib/tilia/dav/mount/plugin.rb +62 -0
  202. data/lib/tilia/dav/node.rb +36 -0
  203. data/lib/tilia/dav/partial_update.rb +8 -0
  204. data/lib/tilia/dav/partial_update/i_patch_support.rb +40 -0
  205. data/lib/tilia/dav/partial_update/plugin.rb +179 -0
  206. data/lib/tilia/dav/prop_find.rb +262 -0
  207. data/lib/tilia/dav/prop_patch.rb +278 -0
  208. data/lib/tilia/dav/property_storage.rb +8 -0
  209. data/lib/tilia/dav/property_storage/backend.rb +10 -0
  210. data/lib/tilia/dav/property_storage/backend/backend_interface.rb +69 -0
  211. data/lib/tilia/dav/property_storage/backend/sequel.rb +192 -0
  212. data/lib/tilia/dav/property_storage/plugin.rb +131 -0
  213. data/lib/tilia/dav/server.rb +1388 -0
  214. data/lib/tilia/dav/server_plugin.rb +81 -0
  215. data/lib/tilia/dav/simple_collection.rb +71 -0
  216. data/lib/tilia/dav/simple_file.rb +82 -0
  217. data/lib/tilia/dav/string_util.rb +68 -0
  218. data/lib/tilia/dav/sync.rb +8 -0
  219. data/lib/tilia/dav/sync/i_sync_collection.rb +80 -0
  220. data/lib/tilia/dav/sync/plugin.rb +225 -0
  221. data/lib/tilia/dav/temporary_file_filter_plugin.rb +248 -0
  222. data/lib/tilia/dav/tree.rb +270 -0
  223. data/lib/tilia/dav/uuid_util.rb +45 -0
  224. data/lib/tilia/dav/version.rb +9 -0
  225. data/lib/tilia/dav/xml.rb +11 -0
  226. data/lib/tilia/dav/xml/element.rb +10 -0
  227. data/lib/tilia/dav/xml/element/prop.rb +92 -0
  228. data/lib/tilia/dav/xml/element/response.rb +188 -0
  229. data/lib/tilia/dav/xml/property.rb +16 -0
  230. data/lib/tilia/dav/xml/property/complex.rb +76 -0
  231. data/lib/tilia/dav/xml/property/get_last_modified.rb +79 -0
  232. data/lib/tilia/dav/xml/property/href.rb +137 -0
  233. data/lib/tilia/dav/xml/property/lock_discovery.rb +89 -0
  234. data/lib/tilia/dav/xml/property/resource_type.rb +96 -0
  235. data/lib/tilia/dav/xml/property/supported_lock.rb +48 -0
  236. data/lib/tilia/dav/xml/property/supported_method_set.rb +101 -0
  237. data/lib/tilia/dav/xml/property/supported_report_set.rb +118 -0
  238. data/lib/tilia/dav/xml/request.rb +13 -0
  239. data/lib/tilia/dav/xml/request/lock.rb +67 -0
  240. data/lib/tilia/dav/xml/request/mk_col.rb +69 -0
  241. data/lib/tilia/dav/xml/request/prop_find.rb +70 -0
  242. data/lib/tilia/dav/xml/request/prop_patch.rb +101 -0
  243. data/lib/tilia/dav/xml/request/sync_collection_report.rb +102 -0
  244. data/lib/tilia/dav/xml/response.rb +9 -0
  245. data/lib/tilia/dav/xml/response/multi_status.rb +108 -0
  246. data/lib/tilia/dav/xml/service.rb +42 -0
  247. data/lib/tilia/dav_acl.rb +16 -0
  248. data/lib/tilia/dav_acl/abstract_principal_collection.rb +143 -0
  249. data/lib/tilia/dav_acl/exception.rb +11 -0
  250. data/lib/tilia/dav_acl/exception/ace_conflict.rb +21 -0
  251. data/lib/tilia/dav_acl/exception/need_privileges.rb +65 -0
  252. data/lib/tilia/dav_acl/exception/no_abstract.rb +21 -0
  253. data/lib/tilia/dav_acl/exception/not_recognized_principal.rb +21 -0
  254. data/lib/tilia/dav_acl/exception/not_supported_privilege.rb +21 -0
  255. data/lib/tilia/dav_acl/fs.rb +9 -0
  256. data/lib/tilia/dav_acl/fs/collection.rb +108 -0
  257. data/lib/tilia/dav_acl/fs/file.rb +87 -0
  258. data/lib/tilia/dav_acl/fs/home_collection.rb +148 -0
  259. data/lib/tilia/dav_acl/i_acl.rb +61 -0
  260. data/lib/tilia/dav_acl/i_principal.rb +63 -0
  261. data/lib/tilia/dav_acl/i_principal_collection.rb +52 -0
  262. data/lib/tilia/dav_acl/plugin.rb +1109 -0
  263. data/lib/tilia/dav_acl/principal.rb +213 -0
  264. data/lib/tilia/dav_acl/principal_backend.rb +11 -0
  265. data/lib/tilia/dav_acl/principal_backend/abstract_backend.rb +42 -0
  266. data/lib/tilia/dav_acl/principal_backend/backend_interface.rb +127 -0
  267. data/lib/tilia/dav_acl/principal_backend/create_principal_support.rb +27 -0
  268. data/lib/tilia/dav_acl/principal_backend/sequel.rb +313 -0
  269. data/lib/tilia/dav_acl/principal_collection.rb +117 -0
  270. data/lib/tilia/dav_acl/xml.rb +8 -0
  271. data/lib/tilia/dav_acl/xml/property.rb +13 -0
  272. data/lib/tilia/dav_acl/xml/property/acl.rb +222 -0
  273. data/lib/tilia/dav_acl/xml/property/acl_restrictions.rb +40 -0
  274. data/lib/tilia/dav_acl/xml/property/current_user_privilege_set.rb +125 -0
  275. data/lib/tilia/dav_acl/xml/property/principal.rb +149 -0
  276. data/lib/tilia/dav_acl/xml/property/supported_privilege_set.rb +135 -0
  277. data/lib/tilia/dav_acl/xml/request.rb +11 -0
  278. data/lib/tilia/dav_acl/xml/request/expand_property_report.rb +86 -0
  279. data/lib/tilia/dav_acl/xml/request/principal_property_search_report.rb +111 -0
  280. data/lib/tilia/dav_acl/xml/request/principal_search_property_set_report.rb +49 -0
  281. data/test/cal_dav/backend/abstract_sequel_test.rb +817 -0
  282. data/test/cal_dav/backend/abstract_test.rb +163 -0
  283. data/test/cal_dav/backend/mock.rb +169 -0
  284. data/test/cal_dav/backend/mock_scheduling.rb +84 -0
  285. data/test/cal_dav/backend/mock_sharing.rb +124 -0
  286. data/test/cal_dav/backend/mock_subscription_support.rb +123 -0
  287. data/test/cal_dav/backend/sequel_my_sql_test.rb +102 -0
  288. data/test/cal_dav/backend/sequel_sqlite_test.rb +105 -0
  289. data/test/cal_dav/calendar_home_notifications_test.rb +41 -0
  290. data/test/cal_dav/calendar_home_shared_calendars_test.rb +64 -0
  291. data/test/cal_dav/calendar_home_subscriptions_test.rb +67 -0
  292. data/test/cal_dav/calendar_home_test.rb +144 -0
  293. data/test/cal_dav/calendar_object_test.rb +317 -0
  294. data/test/cal_dav/calendar_query_v_alarm_test.rb +114 -0
  295. data/test/cal_dav/calendar_query_validator_test.rb +820 -0
  296. data/test/cal_dav/calendar_test.rb +203 -0
  297. data/test/cal_dav/expand_events_double_events_test.rb +94 -0
  298. data/test/cal_dav/expand_events_dtstar_tand_dten_dby_day_test.rb +94 -0
  299. data/test/cal_dav/expand_events_dtstar_tand_dtend_test.rb +100 -0
  300. data/test/cal_dav/expand_events_floating_time_test.rb +211 -0
  301. data/test/cal_dav/free_busy_report_test.rb +156 -0
  302. data/test/cal_dav/get_events_by_timerange_test.rb +74 -0
  303. data/test/cal_dav/ics_export_plugin_test.rb +638 -0
  304. data/test/cal_dav/issue166_test.rb +59 -0
  305. data/test/cal_dav/issue172_test.rb +139 -0
  306. data/test/cal_dav/issue203_test.rb +130 -0
  307. data/test/cal_dav/issue205_test.rb +89 -0
  308. data/test/cal_dav/issue211_test.rb +84 -0
  309. data/test/cal_dav/issue220_test.rb +94 -0
  310. data/test/cal_dav/issue228_test.rb +74 -0
  311. data/test/cal_dav/j_cal_transform_test.rb +244 -0
  312. data/test/cal_dav/notifications/collection_test.rb +67 -0
  313. data/test/cal_dav/notifications/node_test.rb +73 -0
  314. data/test/cal_dav/notifications/plugin_test.rb +144 -0
  315. data/test/cal_dav/plugin_test.rb +1049 -0
  316. data/test/cal_dav/principal/collection_test.rb +19 -0
  317. data/test/cal_dav/principal/proxy_read_test.rb +67 -0
  318. data/test/cal_dav/principal/proxy_write_test.rb +29 -0
  319. data/test/cal_dav/principal/user_test.rb +91 -0
  320. data/test/cal_dav/schedule/deliver_new_event_test.rb +81 -0
  321. data/test/cal_dav/schedule/free_busy_request_test.rb +565 -0
  322. data/test/cal_dav/schedule/i_mip/mock_plugin.rb +40 -0
  323. data/test/cal_dav/schedule/i_mip_plugin_test.rb +196 -0
  324. data/test/cal_dav/schedule/inbox_test.rb +150 -0
  325. data/test/cal_dav/schedule/outbox_post_test.rb +124 -0
  326. data/test/cal_dav/schedule/outbox_test.rb +76 -0
  327. data/test/cal_dav/schedule/plugin_basic_test.rb +39 -0
  328. data/test/cal_dav/schedule/plugin_properties_test.rb +96 -0
  329. data/test/cal_dav/schedule/plugin_properties_with_shared_calendar_test.rb +69 -0
  330. data/test/cal_dav/schedule/schedule_deliver_test.rb +605 -0
  331. data/test/cal_dav/schedule/scheduling_object_test.rb +327 -0
  332. data/test/cal_dav/shareable_calendar_test.rb +58 -0
  333. data/test/cal_dav/shared_calendar_test.rb +189 -0
  334. data/test/cal_dav/sharing_plugin_test.rb +373 -0
  335. data/test/cal_dav/subscriptions/create_subscription_test.rb +115 -0
  336. data/test/cal_dav/subscriptions/plugin_test.rb +46 -0
  337. data/test/cal_dav/subscriptions/subscription_test.rb +119 -0
  338. data/test/cal_dav/test_util.rb +164 -0
  339. data/test/cal_dav/validate_i_cal_test.rb +219 -0
  340. data/test/cal_dav/xml/notification/invite_reply_test.rb +136 -0
  341. data/test/cal_dav/xml/notification/invite_test.rb +225 -0
  342. data/test/cal_dav/xml/notification/system_status_test.rb +63 -0
  343. data/test/cal_dav/xml/property/allowed_sharing_modes_test.rb +34 -0
  344. data/test/cal_dav/xml/property/email_address_set_test.rb +35 -0
  345. data/test/cal_dav/xml/property/invite_test.rb +173 -0
  346. data/test/cal_dav/xml/property/schedule_calendar_transp_test.rb +96 -0
  347. data/test/cal_dav/xml/property/supported_calendar_component_set_test.rb +76 -0
  348. data/test/cal_dav/xml/property/supported_calendar_data_test.rb +32 -0
  349. data/test/cal_dav/xml/property/supported_collation_set_test.rb +33 -0
  350. data/test/cal_dav/xml/request/calendar_query_report_test.rb +339 -0
  351. data/test/cal_dav/xml/request/invite_reply_test.rb +68 -0
  352. data/test/cal_dav/xml/request/share_test.rb +79 -0
  353. data/test/card_dav/abstract_plugin_test.rb +24 -0
  354. data/test/card_dav/address_book_home_test.rb +128 -0
  355. data/test/card_dav/address_book_query_test.rb +303 -0
  356. data/test/card_dav/address_book_root_test.rb +26 -0
  357. data/test/card_dav/address_book_test.rb +166 -0
  358. data/test/card_dav/backend/abstract_sequel_test.rb +302 -0
  359. data/test/card_dav/backend/mock.rb +122 -0
  360. data/test/card_dav/backend/sequel_my_sql_test.rb +56 -0
  361. data/test/card_dav/backend/sequel_sqlite_test.rb +59 -0
  362. data/test/card_dav/card_test.rb +164 -0
  363. data/test/card_dav/i_directory_test.rb +22 -0
  364. data/test/card_dav/multi_get_test.rb +97 -0
  365. data/test/card_dav/plugin_test.rb +87 -0
  366. data/test/card_dav/sogo_strip_content_type_test.rb +63 -0
  367. data/test/card_dav/test_util.rb +51 -0
  368. data/test/card_dav/validate_filter_test.rb +210 -0
  369. data/test/card_dav/validate_v_card_test.rb +143 -0
  370. data/test/card_dav/vcf_export_test.rb +66 -0
  371. data/test/card_dav/xml/property/supported_address_data_test.rb +34 -0
  372. data/test/card_dav/xml/property/supported_collation_set_test.rb +34 -0
  373. data/test/card_dav/xml/request/address_book_query_report_test.rb +276 -0
  374. data/test/dav/abstract_server.rb +36 -0
  375. data/test/dav/auth/backend/abstract_basic_test.rb +74 -0
  376. data/test/dav/auth/backend/abstract_digest_test.rb +114 -0
  377. data/test/dav/auth/backend/abstract_sequel_test.rb +25 -0
  378. data/test/dav/auth/backend/apache_test.rb +60 -0
  379. data/test/dav/auth/backend/basic_call_back_test.rb +33 -0
  380. data/test/dav/auth/backend/file_test.rb +43 -0
  381. data/test/dav/auth/backend/mock.rb +73 -0
  382. data/test/dav/auth/backend/sequel_my_sql_test.rb +32 -0
  383. data/test/dav/auth/backend/sequel_sqlite_test.rb +21 -0
  384. data/test/dav/auth/plugin_test.rb +92 -0
  385. data/test/dav/basic_node_test.rb +143 -0
  386. data/test/dav/browser/guess_content_type_test.rb +44 -0
  387. data/test/dav/browser/map_get_to_prop_find_test.rb +37 -0
  388. data/test/dav/browser/plugin_test.rb +165 -0
  389. data/test/dav/browser/prop_find_all_test.rb +59 -0
  390. data/test/dav/client_mock.rb +24 -0
  391. data/test/dav/client_test.rb +231 -0
  392. data/test/dav/copy_test.rb +33 -0
  393. data/test/dav/exception/locked_test.rb +61 -0
  394. data/test/dav/exception/payment_required_test.rb +14 -0
  395. data/test/dav/exception/service_unavailable_test.rb +14 -0
  396. data/test/dav/exception/too_many_matches_test.rb +31 -0
  397. data/test/dav/exception_test.rb +24 -0
  398. data/test/dav/fs_ext/file_test.rb +72 -0
  399. data/test/dav/fs_ext/server_test.rb +251 -0
  400. data/test/dav/get_if_conditions_test.rb +299 -0
  401. data/test/dav/http_delete_test.rb +110 -0
  402. data/test/dav/http_get_test.rb +130 -0
  403. data/test/dav/http_head_test.rb +80 -0
  404. data/test/dav/http_move_test.rb +105 -0
  405. data/test/dav/http_prefer_parsing_test.rb +186 -0
  406. data/test/dav/http_put_test.rb +271 -0
  407. data/test/dav/issue33_test.rb +90 -0
  408. data/test/dav/locks/backend/abstract_test.rb +160 -0
  409. data/test/dav/locks/backend/file_test.rb +24 -0
  410. data/test/dav/locks/backend/mock.rb +82 -0
  411. data/test/dav/locks/backend/sequel_my_sql_test.rb +32 -0
  412. data/test/dav/locks/backend/sequel_test.rb +19 -0
  413. data/test/dav/locks/ms_word_test.rb +119 -0
  414. data/test/dav/locks/plugin2_test.rb +61 -0
  415. data/test/dav/locks/plugin_test.rb +896 -0
  416. data/test/dav/mock/collection.rb +113 -0
  417. data/test/dav/mock/file.rb +100 -0
  418. data/test/dav/mock/properties_collection.rb +80 -0
  419. data/test/dav/mock/streaming_file.rb +66 -0
  420. data/test/dav/mount/plugin_test.rb +48 -0
  421. data/test/dav/object_tree_test.rb +65 -0
  422. data/test/dav/partial_update/file_mock.rb +92 -0
  423. data/test/dav/partial_update/plugin_test.rb +125 -0
  424. data/test/dav/partial_update/specification_test.rb +77 -0
  425. data/test/dav/prop_find_test.rb +87 -0
  426. data/test/dav/prop_patch_test.rb +367 -0
  427. data/test/dav/property_storage/backend/abstract_sequel_test.rb +147 -0
  428. data/test/dav/property_storage/backend/mock.rb +96 -0
  429. data/test/dav/property_storage/backend/sequel_mysql_test.rb +32 -0
  430. data/test/dav/property_storage/backend/sequel_sqlite_test.rb +31 -0
  431. data/test/dav/property_storage/plugin_test.rb +90 -0
  432. data/test/dav/server_copy_move_test.rb +164 -0
  433. data/test/dav/server_events_test.rb +105 -0
  434. data/test/dav/server_mkcol_test.rb +337 -0
  435. data/test/dav/server_mock.rb +10 -0
  436. data/test/dav/server_plugin_test.rb +85 -0
  437. data/test/dav/server_precondition_test.rb +253 -0
  438. data/test/dav/server_props_infinite_depth_test.rb +144 -0
  439. data/test/dav/server_props_test.rb +182 -0
  440. data/test/dav/server_range_test.rb +262 -0
  441. data/test/dav/server_simple_test.rb +388 -0
  442. data/test/dav/server_update_properties_test.rb +93 -0
  443. data/test/dav/simple_file_test.rb +17 -0
  444. data/test/dav/string_util_test.rb +92 -0
  445. data/test/dav/sync/mock_sync_collection.rb +141 -0
  446. data/test/dav/sync/plugin_test.rb +491 -0
  447. data/test/dav/sync_token_property_test.rb +105 -0
  448. data/test/dav/temporary_file_filter_test.rb +179 -0
  449. data/test/dav/test_plugin.rb +24 -0
  450. data/test/dav/tree_test.rb +201 -0
  451. data/test/dav/uuid_util_test.rb +14 -0
  452. data/test/dav/xml/element/prop_test.rb +121 -0
  453. data/test/dav/xml/element/response_test.rb +202 -0
  454. data/test/dav/xml/property/href_test.rb +112 -0
  455. data/test/dav/xml/property/last_modified_test.rb +52 -0
  456. data/test/dav/xml/property/lock_discovery_test.rb +79 -0
  457. data/test/dav/xml/property/supported_method_set_test.rb +54 -0
  458. data/test/dav/xml/property/supported_report_set_test.rb +109 -0
  459. data/test/dav/xml/request/prop_find_test.rb +45 -0
  460. data/test/dav/xml/request/prop_patch_test.rb +47 -0
  461. data/test/dav/xml/request/sync_collection_test.rb +89 -0
  462. data/test/dav/xml/xml_tester.rb +35 -0
  463. data/test/dav_acl/acl_method_test.rb +299 -0
  464. data/test/dav_acl/allow_access_test.rb +94 -0
  465. data/test/dav_acl/block_access_test.rb +161 -0
  466. data/test/dav_acl/exception/ace_conflict_test.rb +33 -0
  467. data/test/dav_acl/exception/need_privileges_exception_test.rb +43 -0
  468. data/test/dav_acl/exception/no_abstract_test.rb +33 -0
  469. data/test/dav_acl/exception/not_recognized_principal_test.rb +33 -0
  470. data/test/dav_acl/exception/not_supported_privilege_test.rb +33 -0
  471. data/test/dav_acl/expand_properties_test.rb +265 -0
  472. data/test/dav_acl/fs/collection_test.rb +39 -0
  473. data/test/dav_acl/fs/file_test.rb +47 -0
  474. data/test/dav_acl/fs/home_collection_test.rb +82 -0
  475. data/test/dav_acl/mock_acl_node.rb +27 -0
  476. data/test/dav_acl/mock_principal.rb +27 -0
  477. data/test/dav_acl/plugin_admin_test.rb +60 -0
  478. data/test/dav_acl/plugin_properties_test.rb +346 -0
  479. data/test/dav_acl/plugin_update_properties_test.rb +82 -0
  480. data/test/dav_acl/principal_backend/abstract_sequel_test.rb +159 -0
  481. data/test/dav_acl/principal_backend/mock.rb +150 -0
  482. data/test/dav_acl/principal_backend/sequel_my_sql_test.rb +43 -0
  483. data/test/dav_acl/principal_backend/sequel_sqlite_test.rb +31 -0
  484. data/test/dav_acl/principal_collection_test.rb +44 -0
  485. data/test/dav_acl/principal_property_search_test.rb +354 -0
  486. data/test/dav_acl/principal_search_property_set_test.rb +125 -0
  487. data/test/dav_acl/principal_test.rb +181 -0
  488. data/test/dav_acl/simple_plugin_test.rb +320 -0
  489. data/test/dav_acl/xml/property/acl_restrictions_test.rb +28 -0
  490. data/test/dav_acl/xml/property/acl_test.rb +325 -0
  491. data/test/dav_acl/xml/property/current_user_privilege_set_test.rb +77 -0
  492. data/test/dav_acl/xml/property/principal_test.rb +158 -0
  493. data/test/dav_acl/xml/property/supported_privilege_set_test.rb +109 -0
  494. data/test/dav_server_test.rb +225 -0
  495. data/test/http/response_mock.rb +16 -0
  496. data/test/http/sapi_mock.rb +29 -0
  497. data/test/test_helper.rb +176 -0
  498. data/tilia-dav.gemspec +28 -0
  499. metadata +726 -0
@@ -0,0 +1,80 @@
1
+ module Tilia
2
+ module CardDav
3
+ module Backend
4
+ # WebDAV-sync support for CardDAV backends.
5
+ #
6
+ # In order for backends to advertise support for WebDAV-sync, this interface
7
+ # must be implemented.
8
+ #
9
+ # Implementing this can result in a significant reduction of bandwidth and CPU
10
+ # time.
11
+ #
12
+ # For this to work, you _must_ return a {http://sabredav.org/ns}sync-token
13
+ # property from getAddressBooksForUser.
14
+ #
15
+ # @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
16
+ # @author Evert Pot (http://evertpot.com/)
17
+ # @license http://sabre.io/license/ Modified BSD License
18
+ module SyncSupport
19
+ include BackendInterface
20
+
21
+ # The getChanges method returns all the changes that have happened, since
22
+ # the specified syncToken in the specified address book.
23
+ #
24
+ # This function should return an array, such as the following:
25
+ #
26
+ # [
27
+ # 'syncToken' => 'The current synctoken',
28
+ # 'added' => [
29
+ # 'new.txt',
30
+ # ],
31
+ # 'modified' => [
32
+ # 'modified.txt',
33
+ # ],
34
+ # 'deleted' => [
35
+ # 'foo.php.bak',
36
+ # 'old.txt'
37
+ # ]
38
+ # ]
39
+ #
40
+ # The returned syncToken property should reflect the *current* syncToken
41
+ # of the calendar, as reported in the {http://sabredav.org/ns}sync-token
42
+ # property. This is needed here too, to ensure the operation is atomic.
43
+ #
44
+ # If the sync_token argument is specified as null, this is an initial
45
+ # sync, and all members should be reported.
46
+ #
47
+ # The modified property is an array of nodenames that have changed since
48
+ # the last token.
49
+ #
50
+ # The deleted property is an array with nodenames, that have been deleted
51
+ # from collection.
52
+ #
53
+ # The sync_level argument is basically the 'depth' of the report. If it's
54
+ # 1, you only have to report changes that happened only directly in
55
+ # immediate descendants. If it's 2, it should also include changes from
56
+ # the nodes below the child collections. (grandchildren)
57
+ #
58
+ # The limit argument allows a client to specify how many results should
59
+ # be returned at most. If the limit is not specified, it should be treated
60
+ # as infinite.
61
+ #
62
+ # If the limit (infinite or not) is higher than you're willing to return,
63
+ # you should throw a Sabre\DAV\Exception\Too_much_matches exception.
64
+ #
65
+ # If the syncToken is expired (due to data cleanup) or unknown, you must
66
+ # return null.
67
+ #
68
+ # The limit is 'suggestive'. You are free to ignore it.
69
+ #
70
+ # @param string address_book_id
71
+ # @param string sync_token
72
+ # @param int sync_level
73
+ # @param int limit
74
+ # @return array
75
+ def changes_for_address_book(address_book_id, sync_token, sync_level, limit = nil)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,193 @@
1
+ module Tilia
2
+ module CardDav
3
+ # The Card object represents a single Card from an addressbook
4
+ class Card < Dav::File
5
+ include ICard
6
+ include DavAcl::IAcl
7
+
8
+ protected
9
+
10
+ # CardDAV backend
11
+ #
12
+ # @var Backend\BackendInterface
13
+ attr_accessor :carddav_backend
14
+
15
+ # Array with information about this Card
16
+ #
17
+ # @var array
18
+ attr_accessor :card_data
19
+
20
+ # Array with information about the containing addressbook
21
+ #
22
+ # @var array
23
+ attr_accessor :address_book_info
24
+
25
+ public
26
+
27
+ # Constructor
28
+ #
29
+ # @param Backend\BackendInterface carddav_backend
30
+ # @param array address_book_info
31
+ # @param array card_data
32
+ def initialize(carddav_backend, address_book_info, card_data)
33
+ @carddav_backend = carddav_backend
34
+ @address_book_info = address_book_info
35
+ @card_data = card_data
36
+ end
37
+
38
+ # Returns the uri for this object
39
+ #
40
+ # @return string
41
+ def name
42
+ @card_data['uri']
43
+ end
44
+
45
+ # Returns the VCard-formatted object
46
+ #
47
+ # @return string
48
+ def get
49
+ # Pre-populating 'carddata' is optional. If we don't yet have it
50
+ # already, we fetch it from the backend.
51
+ @card_data = @carddav_backend.card(@address_book_info['id'], @card_data['uri']) unless @card_data.key?('carddata')
52
+
53
+ @card_data['carddata']
54
+ end
55
+
56
+ # Updates the VCard-formatted object
57
+ #
58
+ # @param string card_data
59
+ # @return string|null
60
+ def put(card_data)
61
+ card_data = card_data.read unless card_data.is_a?(String)
62
+
63
+ # Converting to UTF-8, if needed
64
+ card_data = Dav::StringUtil.ensure_utf8(card_data)
65
+
66
+ etag = @carddav_backend.update_card(@address_book_info['id'], @card_data['uri'], card_data)
67
+ @card_data['carddata'] = card_data
68
+ @card_data['etag'] = etag
69
+
70
+ etag
71
+ end
72
+
73
+ # Deletes the card
74
+ #
75
+ # @return void
76
+ def delete
77
+ @carddav_backend.delete_card(@address_book_info['id'], @card_data['uri'])
78
+ end
79
+
80
+ # Returns the mime content-type
81
+ #
82
+ # @return string
83
+ def content_type
84
+ 'text/vcard; charset=utf-8'
85
+ end
86
+
87
+ # Returns an ETag for this object
88
+ #
89
+ # @return string
90
+ def etag
91
+ if @card_data.key?('etag')
92
+ return @card_data['etag']
93
+ else
94
+ data = get
95
+ if data.is_a?(String)
96
+ return "\"#{Digest::MD5.hexdigest(data)}\""
97
+ else
98
+ # We refuse to calculate the md5 if it's a stream.
99
+ return nil
100
+ end
101
+ end
102
+ end
103
+
104
+ # Returns the last modification date as a unix timestamp
105
+ #
106
+ # @return int
107
+ def last_modified
108
+ @card_data.key?('lastmodified') ? @card_data['lastmodified'] : nil
109
+ end
110
+
111
+ # Returns the size of this object in bytes
112
+ #
113
+ # @return int
114
+ def size
115
+ if @card_data.key?('size')
116
+ return @card_data['size']
117
+ else
118
+ return get.size
119
+ end
120
+ end
121
+
122
+ # Returns the owner principal
123
+ #
124
+ # This must be a url to a principal, or null if there's no owner
125
+ #
126
+ # @return string|null
127
+ def owner
128
+ @address_book_info['principaluri']
129
+ end
130
+
131
+ # Returns a group principal
132
+ #
133
+ # This must be a url to a principal, or null if there's no owner
134
+ #
135
+ # @return string|null
136
+ def group
137
+ nil
138
+ end
139
+
140
+ # Returns a list of ACE's for this node.
141
+ #
142
+ # Each ACE has the following properties:
143
+ # * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
144
+ # currently the only supported privileges
145
+ # * 'principal', a url to the principal who owns the node
146
+ # * 'protected' (optional), indicating that this ACE is not allowed to
147
+ # be updated.
148
+ #
149
+ # @return array
150
+ def acl
151
+ # An alternative acl may be specified through the cardData array.
152
+ return @card_data['acl'] if @card_data.key?('acl')
153
+
154
+ [
155
+ {
156
+ 'privilege' => '{DAV:}read',
157
+ 'principal' => @address_book_info['principaluri'],
158
+ 'protected' => true
159
+ },
160
+ {
161
+ 'privilege' => '{DAV:}write',
162
+ 'principal' => @address_book_info['principaluri'],
163
+ 'protected' => true
164
+ }
165
+ ]
166
+ end
167
+
168
+ # Updates the ACL
169
+ #
170
+ # This method will receive a list of new ACE's.
171
+ #
172
+ # @param array acl
173
+ # @return void
174
+ def acl=(_acl)
175
+ fail Dav::Exception::MethodNotAllowed, 'Changing ACL is not yet supported'
176
+ end
177
+
178
+ # Returns the list of supported privileges for this node.
179
+ #
180
+ # The returned data structure is a list of nested privileges.
181
+ # See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
182
+ # standard structure.
183
+ #
184
+ # If null is returned from this method, the default privilege set is used,
185
+ # which is fine for most common usecases.
186
+ #
187
+ # @return array|null
188
+ def supported_privilege_set
189
+ nil
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,10 @@
1
+ module Tilia
2
+ module CardDav
3
+ # AddressBook interface
4
+ #
5
+ # Implement this interface to allow a node to be recognized as an addressbook.
6
+ module IAddressBook
7
+ include Dav::ICollection
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module Tilia
2
+ module CardDav
3
+ # Card interface
4
+ #
5
+ # Extend the ICard interface to allow your custom nodes to be picked up as
6
+ # 'Cards'.
7
+ module ICard
8
+ include Dav::IFile
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module Tilia
2
+ module CardDav
3
+ # IDirectory interface
4
+ #
5
+ # Implement this interface to have an addressbook marked as a 'directory'. A
6
+ # directory is an (often) global addressbook.
7
+ #
8
+ # A full description can be found in the IETF draft:
9
+ # - draft-daboo-carddav-directory-gateway
10
+ module IDirectory
11
+ include IAddressBook
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,724 @@
1
+ module Tilia
2
+ module CardDav
3
+ # CardDAV plugin
4
+ #
5
+ # The CardDAV plugin adds CardDAV functionality to the WebDAV server
6
+ class Plugin < Dav::ServerPlugin
7
+ # Url to the addressbooks
8
+ ADDRESSBOOK_ROOT = 'addressbooks'
9
+
10
+ # xml namespace for CardDAV elements
11
+ NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav'
12
+
13
+ # Add urls to this property to have them automatically exposed as
14
+ # 'directories' to the user.
15
+ #
16
+ # @var array
17
+ attr_accessor :directories
18
+
19
+ protected
20
+
21
+ # Server class
22
+ #
23
+ # @var Sabre\DAV\Server
24
+ attr_accessor :server
25
+
26
+ # The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data,
27
+ # which can hold up to 2^24 = 16777216 bytes. This is plenty. We're
28
+ # capping it to 10M here.
29
+ attr_accessor :max_resource_size
30
+
31
+ public
32
+
33
+ # Initializes the plugin
34
+ #
35
+ # @param DAV\Server server
36
+ # @return void
37
+ def setup(server)
38
+ # Events
39
+ @server = server
40
+ @server.on('propFind', method(:prop_find_early))
41
+ @server.on('propFind', method(:prop_find_late), 150)
42
+ @server.on('report', method(:report))
43
+ @server.on('onHTMLActionsPanel', method(:html_actions_panel))
44
+ @server.on('beforeWriteContent', method(:before_write_content))
45
+ @server.on('beforeCreateFile', method(:before_create_file))
46
+ @server.on('afterMethod:GET', method(:http_after_get))
47
+
48
+ @server.xml.namespace_map[NS_CARDDAV] = 'card'
49
+
50
+ @server.xml.element_map["{#{NS_CARDDAV}}addressbook-query"] = Xml::Request::AddressBookQueryReport
51
+ @server.xml.element_map["{#{NS_CARDDAV}}addressbook-multiget"] = Xml::Request::AddressBookMultiGetReport
52
+
53
+ # Mapping Interfaces to {DAV:}resourcetype values
54
+ @server.resource_type_mapping[IAddressBook] = "{#{NS_CARDDAV}}addressbook"
55
+ @server.resource_type_mapping[IDirectory] = "{#{NS_CARDDAV}}directory"
56
+
57
+ # Adding properties that may never be changed
58
+ @server.protected_properties << "{#{NS_CARDDAV}}supported-address-data"
59
+ @server.protected_properties << "{#{NS_CARDDAV}}max-resource-size"
60
+ @server.protected_properties << "{#{NS_CARDDAV}}addressbook-home-set"
61
+ @server.protected_properties << "{#{NS_CARDDAV}}supported-collation-set"
62
+
63
+ @server.xml.element_map['{http://calendarserver.org/ns/}me-card'] = Dav::Xml::Property::Href
64
+ end
65
+
66
+ # Returns a list of supported features.
67
+ #
68
+ # This is used in the DAV: header in the OPTIONS and PROPFIND requests.
69
+ #
70
+ # @return array
71
+ def features
72
+ ['addressbook']
73
+ end
74
+
75
+ # Returns a list of reports this plugin supports.
76
+ #
77
+ # This will be used in the {DAV:}supported-report-set property.
78
+ # Note that you still need to subscribe to the 'report' event to actually
79
+ # implement them
80
+ #
81
+ # @param string uri
82
+ # @return array
83
+ def supported_report_set(uri)
84
+ node = @server.tree.node_for_path(uri)
85
+ if node.is_a?(IAddressBook) || node.is_a?(ICard)
86
+ return [
87
+ "{#{NS_CARDDAV}}addressbook-multiget",
88
+ "{#{NS_CARDDAV}}addressbook-query"
89
+ ]
90
+ end
91
+ []
92
+ end
93
+
94
+ # Adds all CardDAV-specific properties
95
+ #
96
+ # @param DAV\PropFind prop_find
97
+ # @param DAV\INode node
98
+ # @return void
99
+ def prop_find_early(prop_find, node)
100
+ ns = "{#{NS_CARDDAV}}"
101
+
102
+ if node.is_a?(IAddressBook)
103
+ prop_find.handle("#{ns}max-resource-size", @max_resource_size)
104
+ prop_find.handle(
105
+ "#{ns}supported-address-data",
106
+ lambda do
107
+ Xml::Property::SupportedAddressData.new
108
+ end
109
+ )
110
+ prop_find.handle(
111
+ "#{ns}supported-collation-set",
112
+ lambda do
113
+ Xml::Property::SupportedCollationSet.new
114
+ end
115
+ )
116
+ end
117
+
118
+ if node.is_a?(DavAcl::IPrincipal)
119
+ path = prop_find.path
120
+
121
+ prop_find.handle(
122
+ "{#{NS_CARDDAV}}addressbook-home-set",
123
+ lambda do
124
+ Dav::Xml::Property::Href.new(addressbook_home_for_principal(path) + '/')
125
+ end
126
+ )
127
+
128
+ if @directories.any?
129
+ prop_find.handle(
130
+ "{#{NS_CARDDAV}}directory-gateway",
131
+ lambda do
132
+ return Dav::Xml::Property::Href.new(@directories)
133
+ end
134
+ )
135
+ end
136
+ end
137
+
138
+ if node.is_a?(ICard)
139
+ # The address-data property is not supposed to be a 'real'
140
+ # property, but in large chunks of the spec it does act as such.
141
+ # Therefore we simply expose it as a property.
142
+ prop_find.handle(
143
+ "{#{NS_CARDDAV}}address-data",
144
+ lambda do
145
+ val = node.get
146
+ val = val.read unless val.is_a?(String)
147
+
148
+ return val
149
+ end
150
+ )
151
+ end
152
+ end
153
+
154
+ # This functions handles REPORT requests specific to CardDAV
155
+ #
156
+ # @param string report_name
157
+ # @param \DOMNode dom
158
+ # @param mixed path
159
+ # @return bool
160
+ def report(report_name, dom, _path)
161
+ case report_name
162
+ when "{#{NS_CARDDAV}}addressbook-multiget"
163
+ @server.transaction_type = 'report-addressbook-multiget'
164
+ addressbook_multi_get_report(dom)
165
+ return false
166
+ when "{#{NS_CARDDAV}}addressbook-query"
167
+ @server.transaction_type = 'report-addressbook-query'
168
+ addressbook_query_report(dom)
169
+ return false
170
+ else
171
+ return true
172
+ end
173
+ end
174
+
175
+ protected
176
+
177
+ # Returns the addressbook home for a given principal
178
+ #
179
+ # @param string principal
180
+ # @return string
181
+ def addressbook_home_for_principal(principal)
182
+ principal_id = Http::UrlUtil.split_path(principal)[1]
183
+ ADDRESSBOOK_ROOT + '/' + principal_id
184
+ end
185
+
186
+ public
187
+
188
+ # This function handles the addressbook-multiget REPORT.
189
+ #
190
+ # This report is used by the client to fetch the content of a series
191
+ # of urls. Effectively avoiding a lot of redundant requests.
192
+ #
193
+ # @param Xml\Request\AddressBookMultiGetReport report
194
+ # @return void
195
+ def addressbook_multi_get_report(report)
196
+ content_type = report.content_type || ''
197
+ version = report.version
198
+
199
+ content_type << "; version=#{version}" if version
200
+
201
+ vcard_type = negotiate_v_card(content_type)
202
+
203
+ property_list = []
204
+ paths = report.hrefs.map do |href|
205
+ @server.calculate_uri(href)
206
+ end
207
+
208
+ @server.properties_for_multiple_paths(paths, report.properties).each do |_path, props|
209
+ if props[200].key?("{#{NS_CARDDAV}}address-data")
210
+ props[200]["{#{NS_CARDDAV}}address-data"] = convert_v_card(
211
+ props[200]["{#{NS_CARDDAV}}address-data"],
212
+ vcard_type
213
+ )
214
+ end
215
+
216
+ property_list << props
217
+ end
218
+
219
+ prefer = @server.http_prefer
220
+
221
+ @server.http_response.status = 207
222
+ @server.http_response.update_header('Content-Type', 'application/xml; charset=utf-8')
223
+ @server.http_response.update_header('Vary', 'Brief,Prefer')
224
+ @server.http_response.body = @server.generate_multi_status(property_list, prefer['return'] == 'minimal')
225
+ end
226
+
227
+ # This method is triggered before a file gets updated with new content.
228
+ #
229
+ # This plugin uses this method to ensure that Card nodes receive valid
230
+ # vcard data.
231
+ #
232
+ # @param string path
233
+ # @param DAV\IFile node
234
+ # @param [Box<String, IO>] data
235
+ # @param [Box<Boolean> modified Should be set to true, if this event handler
236
+ # changed &data.
237
+ # @return void
238
+ def before_write_content(_path, node, data, modified)
239
+ return true unless node.is_a?(ICard)
240
+
241
+ validate_v_card(data, modified)
242
+ true # Do not break chain
243
+ end
244
+
245
+ # This method is triggered before a new file is created.
246
+ #
247
+ # This plugin uses this method to ensure that Card nodes receive valid
248
+ # vcard data.
249
+ #
250
+ # @param string path
251
+ # @param [Box<String, IO> data
252
+ # @param DAV\ICollection parent_node
253
+ # @param Box<Boolean> modified Should be set to true, if this event handler
254
+ # changed &data.
255
+ # @return void
256
+ def before_create_file(_path, data, parent_node, modified)
257
+ return true unless parent_node.is_a?(IAddressBook)
258
+
259
+ validate_v_card(data, modified)
260
+ true
261
+ end
262
+
263
+ protected
264
+
265
+ # Checks if the submitted iCalendar data is in fact, valid.
266
+ #
267
+ # An exception is thrown if it's not.
268
+ #
269
+ # @param [Box<String, IO>] data
270
+ # @param [Box<Boolean>] modified Should be set to true, if this event handler
271
+ # changed &data.
272
+ # @return void
273
+ def validate_v_card(data_box, modified_box)
274
+ # If it's a stream, we convert it to a string first.
275
+ data = data_box.value
276
+
277
+ data = data.read unless data.is_a?(String)
278
+
279
+ before = Digest::MD5.hexdigest(data)
280
+
281
+ # Converting the data to unicode, if needed.
282
+ data = Dav::StringUtil.ensure_utf8(data)
283
+
284
+ modified_box.value = true unless Digest::MD5.hexdigest(data) == before
285
+
286
+ begin
287
+ # If the data starts with a [, we can reasonably assume we're dealing
288
+ # with a jCal object.
289
+ if data[0] == '['
290
+ vobj = VObject::Reader.read_json(data)
291
+ # Converting data back to iCalendar, as that's what we
292
+ # technically support everywhere.
293
+ data = vobj.serialize
294
+ modified_box.value = true
295
+ else
296
+ vobj = VObject::Reader.read(data)
297
+ end
298
+ rescue VObject::ParseException => e
299
+ raise Dav::Exception::UnsupportedMediaType, "This resource only supports valid vCard or jCard data. Parse error: #{e}"
300
+ end
301
+
302
+ fail Dav::Exception::UnsupportedMediaType, 'This collection can only support vcard objects.' unless vobj.name == 'VCARD'
303
+
304
+ unless vobj.key?('UID')
305
+ # No UID in vcards is invalid, but we'll just add it in anyway.
306
+ vobj.add('UID', Dav::UuidUtil.uuid)
307
+ data = vobj.serialize
308
+ modified_box.value = true
309
+ end
310
+
311
+ data_box.value = data
312
+
313
+ # Destroy circular references to PHP will GC the object.
314
+ vobj.destroy
315
+ end
316
+
317
+ # This function handles the addressbook-query REPORT
318
+ #
319
+ # This report is used by the client to filter an addressbook based on a
320
+ # complex query.
321
+ #
322
+ # @param Xml\Request\AddressBookQueryReport report
323
+ # @return void
324
+ def addressbook_query_report(report)
325
+ depth = @server.http_depth(0)
326
+
327
+ if depth == 0
328
+ candidate_nodes = [@server.tree.node_for_path(@server.request_uri)]
329
+
330
+ fail Dav::Exception::ReportNotSupported, 'The addressbook-query report is not supported on this url with Depth: 0' unless candidate_nodes[0].is_a?(ICard)
331
+ else
332
+ candidate_nodes = @server.tree.children(@server.request_uri)
333
+ end
334
+
335
+ content_type = report.content_type
336
+ content_type << "; version=#{report.version}" if report.version
337
+
338
+ vcard_type = negotiate_v_card(content_type)
339
+
340
+ valid_nodes = []
341
+ candidate_nodes.each do |node|
342
+ next unless node.is_a?(ICard)
343
+
344
+ blob = node.get
345
+ blob = blob.read unless blob.is_a?(String)
346
+
347
+ next unless validate_filters(blob, report.filters, report.test)
348
+
349
+ valid_nodes << node
350
+
351
+ if report.limit && report.limit <= valid_nodes.size
352
+ # We hit the maximum number of items, we can stop now.
353
+ break
354
+ end
355
+ end
356
+
357
+ result = []
358
+ valid_nodes.each do |valid_node|
359
+ if depth == 0
360
+ href = @server.request_uri
361
+ else
362
+ href = "#{@server.request_uri}/#{valid_node.name}"
363
+ end
364
+
365
+ props = @server.properties_for_path(href, report.properties, 0).first
366
+
367
+ if props[200].key?("{#{NS_CARDDAV}}address-data")
368
+ props[200]["{#{NS_CARDDAV}}address-data"] = convert_v_card(
369
+ props[200]["{#{NS_CARDDAV}}address-data"],
370
+ vcard_type
371
+ )
372
+ end
373
+
374
+ result << props
375
+ end
376
+
377
+ prefer = @server.http_prefer
378
+
379
+ @server.http_response.status = 207
380
+ @server.http_response.update_header('Content-Type', 'application/xml; charset=utf-8')
381
+ @server.http_response.update_header('Vary', 'Brief,Prefer')
382
+ @server.http_response.body = @server.generate_multi_status(result, prefer['return'] == 'minimal')
383
+ end
384
+
385
+ public
386
+
387
+ # Validates if a vcard makes it throught a list of filters.
388
+ #
389
+ # @param string vcard_data
390
+ # @param array filters
391
+ # @param string test anyof or allof (which means OR or AND)
392
+ # @return bool
393
+ def validate_filters(vcard_data, filters, test)
394
+ return true if filters.empty?
395
+
396
+ vcard = VObject::Reader.read(vcard_data)
397
+
398
+ filters.each do |filter|
399
+ is_defined = vcard.key?(filter['name'])
400
+
401
+ if filter['is-not-defined']
402
+ if is_defined
403
+ success = false
404
+ else
405
+ success = true
406
+ end
407
+ elsif (filter['param-filters'].empty? && filter['text-matches'].empty?) || !is_defined
408
+ # We only need to check for existence
409
+ success = is_defined
410
+ else
411
+ v_properties = vcard.select(filter['name'])
412
+
413
+ results = []
414
+ results << validate_param_filters(v_properties, filter['param-filters'], filter['test']) if filter['param-filters']
415
+
416
+ if filter['text-matches']
417
+ texts = []
418
+ v_properties.each do |v_property|
419
+ texts << v_property.value
420
+ end
421
+
422
+ results << validate_text_matches(texts, filter['text-matches'], filter['test'])
423
+ end
424
+
425
+ if results.size == 1
426
+ success = results[0]
427
+ else
428
+ if filter['test'] == 'anyof'
429
+ success = results[0] || results[1]
430
+ else
431
+ success = results[0] && results[1]
432
+ end
433
+ end
434
+ end
435
+
436
+ # There are two conditions where we can already determine whether
437
+ # or not this filter succeeds.
438
+ if test == 'anyof' && success
439
+ # Destroy circular references to PHP will GC the object.
440
+ vcard.destroy
441
+
442
+ return true
443
+ end
444
+
445
+ next unless test == 'allof' && !success
446
+ vcard.destroy
447
+
448
+ return false
449
+ end
450
+
451
+ # Destroy circular references to PHP will GC the object.
452
+ vcard.destroy
453
+
454
+ # If we got all the way here, it means we haven't been able to
455
+ # determine early if the test failed or not.
456
+ #
457
+ # This implies for 'anyof' that the test failed, and for 'allof' that
458
+ # we succeeded. Sounds weird, but makes sense.
459
+ test == 'allof'
460
+ end
461
+
462
+ protected
463
+
464
+ # Validates if a param-filter can be applied to a specific property.
465
+ #
466
+ # @todo currently we're only validating the first parameter of the passed
467
+ # property. Any subsequence parameters with the same name are
468
+ # ignored.
469
+ # @param array v_properties
470
+ # @param array filters
471
+ # @param string test
472
+ # @return bool
473
+ def validate_param_filters(v_properties, filters, test)
474
+ filters.each do |filter|
475
+ is_defined = false
476
+ v_properties.each do |v_property|
477
+ is_defined = v_property.key?(filter['name'])
478
+ break if is_defined
479
+ end
480
+
481
+ if filter['is-not-defined']
482
+ if is_defined
483
+ success = false
484
+ else
485
+ success = true
486
+ end
487
+ # If there's no text-match, we can just check for existence
488
+ elsif !filter['text-match'] || !is_defined
489
+ success = is_defined
490
+ else
491
+ success = false
492
+ v_properties.each do |v_property|
493
+ # If we got all the way here, we'll need to validate the
494
+ # text-match filter.
495
+ success = Dav::StringUtil.text_match(v_property[filter['name']].value, filter['text-match']['value'], filter['text-match']['collation'], filter['text-match']['match-type'])
496
+ break if success
497
+ end
498
+
499
+ success = !success if filter['text-match']['negate-condition']
500
+ end
501
+
502
+ # There are two conditions where we can already determine whether
503
+ # or not this filter succeeds.
504
+ return true if test == 'anyof' && success
505
+ return false if test == 'allof' && !success
506
+ end
507
+
508
+ # If we got all the way here, it means we haven't been able to
509
+ # determine early if the test failed or not.
510
+ #
511
+ # This implies for 'anyof' that the test failed, and for 'allof' that
512
+ # we succeeded. Sounds weird, but makes sense.
513
+ test == 'allof'
514
+ end
515
+
516
+ # Validates if a text-filter can be applied to a specific property.
517
+ #
518
+ # @param array texts
519
+ # @param array filters
520
+ # @param string test
521
+ # @return bool
522
+ def validate_text_matches(texts, filters, test)
523
+ filters.each do |filter|
524
+ success = false
525
+ texts.each do |haystack|
526
+ success = Dav::StringUtil.text_match(haystack, filter['value'], filter['collation'], filter['match-type'])
527
+
528
+ # Breaking on the first match
529
+ break if success
530
+ end
531
+ success = !success if filter['negate-condition']
532
+
533
+ return true if success && test == 'anyof'
534
+
535
+ return false if !success && test == 'allof'
536
+ end
537
+
538
+ # If we got all the way here, it means we haven't been able to
539
+ # determine early if the test failed or not.
540
+ #
541
+ # This implies for 'anyof' that the test failed, and for 'allof' that
542
+ # we succeeded. Sounds weird, but makes sense.
543
+ test == 'allof'
544
+ end
545
+
546
+ public
547
+
548
+ # This event is triggered when fetching properties.
549
+ #
550
+ # This event is scheduled late in the process, after most work for
551
+ # propfind has been done.
552
+ #
553
+ # @param DAV\PropFind prop_find
554
+ # @param DAV\INode node
555
+ # @return void
556
+ def prop_find_late(prop_find, _node)
557
+ # If the request was made using the SOGO connector, we must rewrite
558
+ # the content-type property. By default SabreDAV will send back
559
+ # text/x-vcard; charset=utf-8, but for SOGO we must strip that last
560
+ # part.
561
+ return unless (@server.http_request.header('User-Agent') || '').index('Thunderbird')
562
+
563
+ content_type = prop_find.get('{DAV:}getcontenttype')
564
+ part = content_type.split(';').first
565
+ if part == 'text/x-vcard' || part == 'text/vcard'
566
+ prop_find.set('{DAV:}getcontenttype', 'text/x-vcard')
567
+ end
568
+ end
569
+
570
+ # This method is used to generate HTML output for the
571
+ # Sabre\DAV\Browser\Plugin. This allows us to generate an interface users
572
+ # can use to create new addressbooks.
573
+ #
574
+ # @param DAV\INode node
575
+ # @param [Box] output
576
+ # @return bool
577
+ def html_actions_panel(node, output)
578
+ return false unless node.is_a?(AddressBookHome)
579
+
580
+ output.value << <<HTML
581
+ <tr><td colspan="2"><form method="post" action="">
582
+ <h3>Create new address book</h3>
583
+ <input type="hidden" name="sabreAction" value="mkcol" />
584
+ <input type="hidden" name="resourceType" value="{DAV:}collection,{#{NS_CARDDAV}}addressbook" />
585
+ <label>Name (uri):</label> <input type="text" name="name" /><br />
586
+ <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
587
+ <input type="submit" value="create" />
588
+ </form>
589
+ </td></tr>
590
+ HTML
591
+
592
+ false
593
+ end
594
+
595
+ # This event is triggered after GET requests.
596
+ #
597
+ # This is used to transform data into jCal, if this was requested.
598
+ #
599
+ # @param RequestInterface request
600
+ # @param ResponseInterface response
601
+ # @return void
602
+ def http_after_get(request, response)
603
+ return unless (response.header('Content-Type') || '').index('text/vcard')
604
+
605
+ mime_type = Box.new('')
606
+ target = negotiate_v_card(request.header('Accept'), mime_type)
607
+ mime_type = mime_type.value
608
+
609
+ new_body = convert_v_card(
610
+ response.body,
611
+ target
612
+ )
613
+
614
+ response.body = new_body
615
+ response.update_header('Content-Type', "#{mime_type}; charset=utf-8")
616
+ response.update_header('Content-Length', new_body.bytesize)
617
+ end
618
+
619
+ protected
620
+
621
+ # This helper function performs the content-type negotiation for vcards.
622
+ #
623
+ # It will return one of the following strings:
624
+ # 1. vcard3
625
+ # 2. vcard4
626
+ # 3. jcard
627
+ #
628
+ # It defaults to vcard3.
629
+ #
630
+ # @param string input
631
+ # @param string mime_type
632
+ # @return string
633
+ def negotiate_v_card(input, mime_type = Box.new(''))
634
+ result = Http::Util.negotiate(
635
+ input,
636
+ [
637
+ # Most often used mime-type. Version 3
638
+ 'text/x-vcard',
639
+ # The correct standard mime-type. Defaults to version 3 as
640
+ # well.
641
+ 'text/vcard',
642
+ # vCard 4
643
+ 'text/vcard; version=4.0',
644
+ # vCard 3
645
+ 'text/vcard; version=3.0',
646
+ # jCard
647
+ 'application/vcard+json'
648
+ ]
649
+ )
650
+
651
+ mime_type.value = result
652
+ case result
653
+ when 'text/vcard; version=4.0'
654
+ return 'vcard4'
655
+ when 'application/vcard+json'
656
+ return 'jcard'
657
+ else
658
+ mime_type.value = 'text/vcard'
659
+ return 'vcard3'
660
+ end
661
+ end
662
+
663
+ # Converts a vcard blob to a different version, or jcard.
664
+ #
665
+ # @param string data
666
+ # @param string target
667
+ # @return string
668
+ def convert_v_card(data, target)
669
+ data = VObject::Reader.read(data)
670
+ case target
671
+ when 'vcard4'
672
+ data = data.convert(VObject::Document::VCARD40)
673
+ new_result = data.serialize
674
+ when 'jcard'
675
+ data = data.convert(VObject::Document::VCARD40)
676
+ new_result = data.json_serialize.to_json
677
+ else
678
+ data = data.convert(VObject::Document::VCARD30)
679
+ new_result = data.serialize
680
+ end
681
+
682
+ # Destroy circular references to PHP will GC the object.
683
+ data.destroy
684
+
685
+ new_result
686
+ end
687
+
688
+ public
689
+
690
+ # Returns a plugin name.
691
+ #
692
+ # Using this name other plugins will be able to access other plugins
693
+ # using DAV\Server::getPlugin
694
+ #
695
+ # @return string
696
+ def plugin_name
697
+ 'carddav'
698
+ end
699
+
700
+ # Returns a bunch of meta-data about the plugin.
701
+ #
702
+ # Providing this information is optional, and is mainly displayed by the
703
+ # Browser plugin.
704
+ #
705
+ # The description key in the returned array may contain html and will not
706
+ # be sanitized.
707
+ #
708
+ # @return array
709
+ def plugin_info
710
+ {
711
+ 'name' => plugin_name,
712
+ 'description' => 'Adds support for CardDAV (rfc6352)',
713
+ 'link' => 'http://sabre.io/dav/carddav/'
714
+ }
715
+ end
716
+
717
+ # TODO: document
718
+ def initialize
719
+ @directories = []
720
+ @max_resource_size = 10_000_000
721
+ end
722
+ end
723
+ end
724
+ end