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,693 @@
1
+ require 'cgi'
2
+ require 'uri'
3
+ require 'fileutils'
4
+ require 'pathname'
5
+
6
+ module Tilia
7
+ module Dav
8
+ module Browser
9
+ # Browser Plugin
10
+ #
11
+ # This plugin provides a html representation, so that a WebDAV server may be accessed
12
+ # using a browser.
13
+ #
14
+ # The class intercepts GET requests to collection resources and generates a simple
15
+ # html index.
16
+ class Plugin < ServerPlugin
17
+ # reference to server class
18
+ #
19
+ # @var Sabre\DAV\Server
20
+ # RUBY: attr_accessor :server
21
+
22
+ # enablePost turns on the 'actions' panel, which allows people to create
23
+ # folders and upload files straight from a browser.
24
+ #
25
+ # @var bool
26
+ # RUBY: attr_accessor :enable_post
27
+
28
+ # A list of properties that are usually not interesting. This can cut down
29
+ # the browser output a bit by removing the properties that most people
30
+ # will likely not want to see.
31
+ #
32
+ # @var array
33
+ attr_accessor :uninteresting_properties
34
+
35
+ # Creates the object.
36
+ #
37
+ # By default it will allow file creation and uploads.
38
+ # Specify the first argument as false to disable this
39
+ #
40
+ # @param bool enable_post
41
+ def initialize(enable_post = true)
42
+ @enable_post = true
43
+ @uninteresting_properties = [
44
+ '{DAV:}supportedlock',
45
+ '{DAV:}acl-restrictions',
46
+ '{DAV:}supported-privilege-set',
47
+ '{DAV:}supported-method-set'
48
+ ]
49
+ @enable_post = enable_post
50
+ end
51
+
52
+ # Initializes the plugin and subscribes to events
53
+ #
54
+ # @param DAV\Server server
55
+ # @return void
56
+ def setup(server)
57
+ @server = server
58
+ @server.on('method:GET', method(:http_get_early), 90)
59
+ @server.on('method:GET', method(:http_get), 200)
60
+ @server.on('onHTMLActionsPanel', method(:html_actions_panel), 200)
61
+
62
+ @server.on('method:POST', method(:http_post)) if @enable_post
63
+ end
64
+
65
+ # This method intercepts GET requests that have ?sabreAction=info
66
+ # appended to the URL
67
+ #
68
+ # @param RequestInterface request
69
+ # @param ResponseInterface response
70
+ # @return bool
71
+ def http_get_early(request, response)
72
+ params = request.query_parameters
73
+ return http_get(request, response) if params['sabreAction'] == 'info'
74
+ end
75
+
76
+ # This method intercepts GET requests to collections and returns the html
77
+ #
78
+ # @param RequestInterface request
79
+ # @param ResponseInterface response
80
+ # @return bool
81
+ def http_get(request, response)
82
+ # We're not using straight-up $_GET, because we want everything to be
83
+ # unit testable.
84
+ get_vars = request.query_parameters
85
+
86
+ # CSP headers
87
+ @server.http_response.update_header('Content-Security-Policy', "img-src 'self'; style-src 'self';")
88
+
89
+ sabre_action = get_vars['sabreAction']
90
+
91
+ case sabre_action
92
+ when 'asset'
93
+ # Asset handling, such as images
94
+ serve_asset(get_vars['assetName'])
95
+ return false
96
+ when 'plugins'
97
+ response.status = 200
98
+ response.update_header('Content-Type', 'text/html; charset=utf-8')
99
+
100
+ response.body = generate_plugin_listing
101
+ return false
102
+ else # includes "when 'info'"
103
+ begin
104
+ @server.tree.node_for_path(request.path)
105
+ rescue Exception::NotFound => e
106
+ # We're simply stopping when the file isn't found to not interfere
107
+ # with other plugins.
108
+ return nil
109
+ end
110
+
111
+ response.status = 200
112
+ response.update_header('Content-Type', 'text/html; charset=utf-8')
113
+
114
+ response.body = generate_directory_index(request.path)
115
+
116
+ return false
117
+ end
118
+ end
119
+
120
+ # Handles POST requests for tree operations.
121
+ #
122
+ # @param RequestInterface request
123
+ # @param ResponseInterface response
124
+ # @return bool
125
+ def http_post(request, response)
126
+ content_type = request.header('Content-Type')
127
+ content_type = content_type.split(';').first
128
+
129
+ if content_type != 'application/x-www-form-urlencoded' &&
130
+ content_type != 'multipart/form-data'
131
+ return nil
132
+ end
133
+
134
+ post_vars = request.post_data
135
+ return nil unless post_vars.key?('sabreAction')
136
+
137
+ uri = request.path
138
+
139
+ if @server.emit('onBrowserPostAction', [uri, post_vars['sabreAction'], post_vars])
140
+
141
+ case post_vars['sabreAction']
142
+ when 'mkcol'
143
+ if post_vars.key?('name') && !post_vars['name'].blank?
144
+ # Using basename because we won't allow slashes
145
+ folder_name = Http::UrlUtil.split_path(post_vars['name'].strip)[1]
146
+
147
+ if post_vars.key?('resourceType')
148
+ resource_type = post_vars['resourceType'].split(',')
149
+ else
150
+ resource_type = ['{DAV:}collection']
151
+ end
152
+
153
+ properties = {}
154
+ post_vars.each do |var_name, var_value|
155
+ # Any _POST variable in clark notation is treated
156
+ # like a property.
157
+ next unless var_name[0] == '{'
158
+ var_name = var_name.gsub('*DOT*')
159
+ properties[var_name] = var_value
160
+ end
161
+
162
+ mk_col = MkCol.new(resource_type, properties)
163
+ @server.create_collection(uri + '/' + folder_name, mk_col)
164
+ end
165
+ when 'put' # FIXME
166
+ # USE server.http_request.post_data (Rack.POST)
167
+ file = nil
168
+ server.http_request.params.each do |_, value|
169
+ if value.is_a?(Rack::Multipart::UploadedFile)
170
+ file = value
171
+ break
172
+ end
173
+ end
174
+
175
+ if file
176
+ # Making sure we only have a 'basename' component
177
+ new_name = ::File.basename(file.original_filename).strip
178
+
179
+ if post_vars.key?('name') && !post_vars['name'].blank?
180
+ new_name = ::File.basename(post_vars['name']).strip
181
+ end
182
+
183
+ # is there a necessary equivalent in ruby?
184
+ # if (is_uploaded_file(file['tmp_name'])) {
185
+ @server.create_file("#{uri}/#{new_name}", ::File.open(file.path), 'r')
186
+ # end
187
+ end
188
+ end
189
+ end
190
+
191
+ response.update_header('Location', request.url)
192
+ response.status = 302
193
+ false
194
+ end
195
+
196
+ # Escapes a string for html.
197
+ #
198
+ # @param string value
199
+ # @return string
200
+ def escape_html(value)
201
+ CGI.escapeHTML(value)
202
+ end
203
+
204
+ # Generates the html directory index for a given url
205
+ #
206
+ # @param string path
207
+ # @return string
208
+ def generate_directory_index(path)
209
+ html = generate_header(path.blank? ? '/' : path, path)
210
+
211
+ node = @server.tree.node_for_path(path)
212
+ if node.is_a?(ICollection)
213
+ html << '<section><h1>Nodes</h1>'
214
+ html << '<table class="nodeTable">'
215
+
216
+ sub_nodes = @server.properties_for_children(
217
+ path,
218
+ [
219
+ '{DAV:}displayname',
220
+ '{DAV:}resourcetype',
221
+ '{DAV:}getcontenttype',
222
+ '{DAV:}getcontentlength',
223
+ '{DAV:}getlastmodified'
224
+ ]
225
+ )
226
+
227
+ sub_nodes.each do |sub_path, _sub_props|
228
+ sub_node = @server.tree.node_for_path(sub_path)
229
+ full_path = @server.base_uri + Http::UrlUtil.encode_path(sub_path)
230
+ (_, display_path) = Http::UrlUtil.split_path(sub_path)
231
+
232
+ sub_nodes[sub_path]['subNode'] = sub_node
233
+ sub_nodes[sub_path]['fullPath'] = full_path
234
+ sub_nodes[sub_path]['displayPath'] = display_path
235
+ end
236
+ sub_nodes.sort { |a, b| compare_nodes(a, b) }
237
+
238
+ sub_nodes.each do |_, sub_props|
239
+ type = {
240
+ 'string' => 'Unknown',
241
+ 'icon' => 'cog'
242
+ }
243
+ if sub_props.key?('{DAV:}resourcetype')
244
+ type = map_resource_type(sub_props['{DAV:}resourcetype'].value, sub_props['subNode'])
245
+ end
246
+
247
+ html << '<tr>'
248
+ html << '<td class="nameColumn"><a href="' + escape_html(sub_props['fullPath']) + '"><span class="oi" data-glyph="' + escape_html(type['icon']) + '"></span> ' + escape_html(sub_props['displayPath']) + '</a></td>'
249
+ html << '<td class="typeColumn">' + escape_html(type['string']) + '</td>'
250
+ html << '<td>'
251
+ if sub_props.key?('{DAV:}getcontentlength')
252
+ html << escape_html(sub_props['{DAV:}getcontentlength'].to_s + ' bytes')
253
+ end
254
+ html << '</td><td>'
255
+ if sub_props.key?('{DAV:}getlastmodified')
256
+ last_mod = sub_props['{DAV:}getlastmodified'].time
257
+ html << escape_html(last_mod.strftime('%B %e, %Y, %l:%M %P'))
258
+ end
259
+ html << '</td>'
260
+
261
+ button_actions = ''
262
+ if sub_props['sub_node'].is_a?(IFile)
263
+ button_actions = '<a href="' + escape_html(sub_props['fullPath']) + '?sabreAction=info"><span class="oi" data-glyph="info"></span></a>'
264
+ end
265
+
266
+ box = Box.new(button_actions)
267
+ @server.emit('browserButtonActions', [sub_props['fullPath'], sub_props['subNode'], box])
268
+ button_actions = box.value
269
+
270
+ html << "<td>#{button_actions}</td>"
271
+ html << '</tr>'
272
+ end
273
+
274
+ html << '</table>'
275
+
276
+ end
277
+
278
+ html << '</section>'
279
+ html << '<section><h1>Properties</h1>'
280
+ html << '<table class="propTable">'
281
+
282
+ # Allprops request
283
+ prop_find = PropFindAll.new(path)
284
+ properties = @server.properties_by_node(prop_find, node)
285
+
286
+ properties = prop_find.result_for_multi_status[200]
287
+
288
+ properties.each do |prop_name, prop_value|
289
+ if @uninteresting_properties.include?(prop_name)
290
+ html << draw_property_row(prop_name, prop_value)
291
+ end
292
+ end
293
+
294
+ html << '</table>'
295
+ html << '</section>'
296
+
297
+ # Start of generating actions
298
+
299
+ output = ''
300
+ if @enable_post
301
+ box = Box.new(output)
302
+ @server.emit('onHTMLActionsPanel', [node, box])
303
+ output = box.value
304
+ end
305
+
306
+ if output
307
+ html << '<section><h1>Actions</h1>'
308
+ html << '<div class="actions">'
309
+ html << output
310
+ html << '</div>'
311
+ html << '</section>'
312
+ end
313
+
314
+ html << generate_footer
315
+
316
+ @server.http_response.update_header('Content-Security-Policy', "img-src 'self'; style-src 'self';")
317
+
318
+ html
319
+ end
320
+
321
+ # Generates the 'plugins' page.
322
+ #
323
+ # @return string
324
+ def generate_plugin_listing
325
+ html = generate_header('Plugins')
326
+
327
+ html << '<section><h1>Plugins</h1>'
328
+ html << '<table class="propTable">'
329
+ @server.plugins.each do |_, plugin|
330
+ info = plugin.plugin_info
331
+ html << "<tr><th>#{info['name']}</th>"
332
+ html << "<td>#{info['description']}</td>"
333
+ html << '<td>'
334
+ if info.key?('link') && !info['link'].blank?
335
+ html << "<a href=\"#{escape_html(info['link'])}\"><span class=\"oi\" data-glyph=\"book\"></span></a>"
336
+ end
337
+ html << '</td></tr>'
338
+ end
339
+ html << '</table>'
340
+ html << '</section>'
341
+
342
+ html << generate_footer
343
+
344
+ html
345
+ end
346
+
347
+ # Generates the first block of HTML, including the <head> tag and page
348
+ # header.
349
+ #
350
+ # Returns footer.
351
+ #
352
+ # @param string title
353
+ # @param string path
354
+ # @return void
355
+ def generate_header(title, path = nil)
356
+ version = Version::VERSION
357
+
358
+ vars = {
359
+ 'title' => escape_html(title),
360
+ 'favicon' => escape_html(asset_url('favicon.ico')),
361
+ 'style' => escape_html(asset_url('sabredav.css')),
362
+ 'iconstyle' => escape_html(asset_url('openiconic/open-iconic.css')),
363
+ 'logo' => escape_html(asset_url('sabredav.png')),
364
+ 'baseUrl' => @server.base_uri
365
+ }
366
+
367
+ html = <<HTML
368
+ <!DOCTYPE html>
369
+ <html>
370
+ <head>
371
+ <title>#{vars['title']} - tilia/dav #{version}</title>
372
+ <link rel="shortcut icon" href="#{vars['favicon']}" type="image/vnd.microsoft.icon" />
373
+ <link rel="stylesheet" href="#{vars['style']}" type="text/css" />
374
+ <link rel="stylesheet" href="#{vars['iconstyle']}" type="text/css" />
375
+ </head>
376
+ <body>
377
+ <header>
378
+ <div class="logo">
379
+ <a href="#{vars['baseUrl']}"><img src="#{vars['logo']}" alt="tilia/dav" /> #{vars['title']}</a>
380
+ </div>
381
+ </header>
382
+ <nav>
383
+ HTML
384
+
385
+ # If the path is empty, there's no parent.
386
+ if !path.blank?
387
+ parent_uri = Http::UrlUtil.split_path(path).first
388
+ full_path = @server.base_uri + Http::UrlUtil.encode_path(parent_uri)
389
+ html << "<a href=\"#{full_path}\" class=\"btn\">⇤ Go to parent</a>"
390
+ else
391
+ html << '<span class="btn disabled">⇤ Go to parent</span>'
392
+ end
393
+
394
+ html << ' <a href="?sabreAction=plugins" class="btn"><span class="oi" data-glyph="puzzle-piece"></span> Plugins</a>'
395
+ html << '</nav>'
396
+
397
+ html
398
+ end
399
+
400
+ # Generates the page footer.
401
+ #
402
+ # Returns html.
403
+ #
404
+ # @return string
405
+ def generate_footer
406
+ version = Version::VERSION
407
+ <<HTML
408
+ <footer>Generated by TiliaDAV #{version} (c)2015-2015 <a href="http://tiliadav.github.io/">http://tiliadav.github.io/</a></footer>
409
+ </body>
410
+ </html>
411
+ HTML
412
+ end
413
+
414
+ # This method is used to generate the 'actions panel' output for
415
+ # collections.
416
+ #
417
+ # This specifically generates the interfaces for creating new files, and
418
+ # creating new directories.
419
+ #
420
+ # @param DAV\INode node
421
+ # @param mixed output
422
+ # @return void
423
+ def html_actions_panel(node, output)
424
+ return nil unless node.is_a?(ICollection)
425
+
426
+ # We also know fairly certain that if an object is a non-extended
427
+ # SimpleCollection, we won't need to show the panel either.
428
+ return nil if node.class == Tilia::Dav::SimpleCollection
429
+
430
+ output.value << <<FORM
431
+ <form method="post" action="">
432
+ <h3>Create new folder</h3>
433
+ <input type="hidden" name="sabreAction" value="mkcol" />
434
+ <label>Name:</label> <input type="text" name="name" /><br />
435
+ <input type="submit" value="create" />
436
+ </form>
437
+ <form method="post" action="" enctype="multipart/form-data">
438
+ <h3>Upload file</h3>
439
+ <input type="hidden" name="sabreAction" value="put" />
440
+ <label>Name (optional):</label> <input type="text" name="name" /><br />
441
+ <label>File:</label> <input type="file" name="file" /><br />
442
+ <input type="submit" value="upload" />
443
+ </form>
444
+ FORM
445
+ end
446
+
447
+ protected
448
+
449
+ # This method takes a path/name of an asset and turns it into url
450
+ # suiteable for http access.
451
+ #
452
+ # @param string asset_name
453
+ # @return string
454
+ def asset_url(asset_name)
455
+ "#{@server.base_uri}?sabreAction=asset&assetName=#{URI.escape(asset_name)}"
456
+ end
457
+
458
+ # This method returns a local pathname to an asset.
459
+ #
460
+ # @param string asset_name
461
+ # @return string
462
+ # @throws DAV\Exception\NotFound
463
+ def local_asset_path(asset_name)
464
+ asset_dir = ::File.join(::File.dirname(__FILE__), 'assets', '')
465
+ path = asset_dir + asset_name
466
+
467
+ # Making sure people aren't trying to escape from the base path.
468
+ path = path.tr('\\', '/')
469
+ if path.index('/../') || path[-3..-2] == '/..'
470
+ fail Exception::NotFound, 'Path does not exist, or escaping from the base path was detected'
471
+ end
472
+
473
+ begin
474
+ pathname = Pathname.new(path)
475
+ asset_path = Pathname.new(asset_dir)
476
+ if pathname.realpath.to_s.index(asset_path.realpath.to_s) == 0 && ::File.exist?(path)
477
+ return path
478
+ end
479
+ rescue Errno::ENOENT
480
+ raise Exception::NotFound, 'Path does not exist, or escaping from the base path was detected'
481
+ end
482
+
483
+ fail Exception::NotFound, 'Path does not exist, or escaping from the base path was detected'
484
+ end
485
+
486
+ # This method reads an asset from disk and generates a full http response.
487
+ #
488
+ # @param string asset_name
489
+ # @return void
490
+ def serve_asset(asset_name)
491
+ asset_path = local_asset_path(asset_name)
492
+
493
+ # Rudimentary mime type detection
494
+ mime = 'application/octet-stream'
495
+ map = {
496
+ 'ico' => 'image/vnd.microsoft.icon',
497
+ 'png' => 'image/png',
498
+ 'css' => 'text/css'
499
+ }
500
+
501
+ ext = ::File.extname(asset_name)[1..-1]
502
+ mime = map[ext] if map.key?(ext)
503
+
504
+ @server.http_response.update_header('Content-Type', mime)
505
+ @server.http_response.update_header('Content-Length', ::File.size(asset_path))
506
+ @server.http_response.update_header('Cache-Control', 'public, max-age=1209600')
507
+ @server.http_response.status = 200
508
+ @server.http_response.body = ::File.open(asset_path, 'r')
509
+ end
510
+
511
+ # Sort helper function: compares two directory entries based on type and
512
+ # display name. Collections sort above other types.
513
+ #
514
+ # @param array a
515
+ # @param array b
516
+ # @return int
517
+ def compare_nodes(a, b) # FIXME: WTF?
518
+ a = a[1]
519
+ b = b[1]
520
+ type_a = 0
521
+ type_b = 0
522
+ type_a = 1 if a.key?('{DAV:}resourcetype') && a['{DAV:}resourcetype'].value.include?('{DAV:}collection')
523
+ type_b = 1 if b.key?('{DAV:}resourcetype') && b['{DAV:}resourcetype'].value.include?('{DAV:}collection')
524
+
525
+ # If same type, sort alphabetically by filename:
526
+ return (a['displayPath'] <=> b['displayPath']) if type_a == type_b
527
+ ((type_a < type_b) ? 1 : -1)
528
+ end
529
+
530
+ private
531
+
532
+ # Maps a resource type to a human-readable string and icon.
533
+ #
534
+ # @param array resource_types
535
+ # @param INode node
536
+ # @return array
537
+ def map_resource_type(resource_types, node)
538
+ if resource_types.empty?
539
+ if node.is_a?(IFile)
540
+ return {
541
+ 'string' => 'File',
542
+ 'icon' => 'file'
543
+ }
544
+ else
545
+ return {
546
+ 'string' => 'Unknown',
547
+ 'icon' => 'cog'
548
+ }
549
+ end
550
+ end
551
+
552
+ types = {
553
+ '{http://calendarserver.org/ns/}calendar-proxy-write' => {
554
+ 'string' => 'Proxy-Write',
555
+ 'icon' => 'people'
556
+ },
557
+ '{http://calendarserver.org/ns/}calendar-proxy-read' => {
558
+ 'string' => 'Proxy-Read',
559
+ 'icon' => 'people'
560
+ },
561
+ '{urn:ietf:params:xml:ns:caldav}schedule-outbox' => {
562
+ 'string' => 'Outbox',
563
+ 'icon' => 'inbox'
564
+ },
565
+ '{urn:ietf:params:xml:ns:caldav}schedule-inbox' => {
566
+ 'string' => 'Inbox',
567
+ 'icon' => 'inbox'
568
+ },
569
+ '{urn:ietf:params:xml:ns:caldav}calendar' => {
570
+ 'string' => 'Calendar',
571
+ 'icon' => 'calendar'
572
+ },
573
+ '{http://calendarserver.org/ns/}shared-owner' => {
574
+ 'string' => 'Shared',
575
+ 'icon' => 'calendar'
576
+ },
577
+ '{http://calendarserver.org/ns/}subscribed' => {
578
+ 'string' => 'Subscription',
579
+ 'icon' => 'calendar'
580
+ },
581
+ '{urn:ietf:params:xml:ns:carddav}directory' => {
582
+ 'string' => 'Directory',
583
+ 'icon' => 'globe'
584
+ },
585
+ '{urn:ietf:params:xml:ns:carddav}addressbook' => {
586
+ 'string' => 'Address book',
587
+ 'icon' => 'book'
588
+ },
589
+ '{DAV:}principal' => {
590
+ 'string' => 'Principal',
591
+ 'icon' => 'person'
592
+ },
593
+ '{DAV:}collection' => {
594
+ 'string' => 'Collection',
595
+ 'icon' => 'folder'
596
+ }
597
+ }
598
+
599
+ info = {
600
+ 'string' => [],
601
+ 'icon' => 'cog'
602
+ }
603
+
604
+ resource_types.each do |resource_type|
605
+ if types.key?(resource_type)
606
+ info['string'] << types[resource_type]['string']
607
+ else
608
+ info['string'] << resource_type
609
+ end
610
+ end
611
+
612
+ types.each do |key, resource_info|
613
+ if resource_types.include?(key)
614
+ info['icon'] = resource_info['icon']
615
+ break
616
+ end
617
+ end
618
+
619
+ info['string'] = info['string'].join(', ')
620
+
621
+ info
622
+ end
623
+
624
+ # Draws a table row for a property
625
+ #
626
+ # @param string name
627
+ # @param mixed value
628
+ # @return string
629
+ def draw_property_row(name, value)
630
+ html = HtmlOutputHelper.new(
631
+ @server.base_uri,
632
+ @server.xml.namespace_map
633
+ )
634
+
635
+ "<tr><th>#{html.xml_name(name)}</th><td>#{draw_property_value(html, value)}</td></tr>"
636
+ end
637
+
638
+ # Draws a table row for a property
639
+ #
640
+ # @param HtmlOutputHelper html
641
+ # @param mixed value
642
+ # @return string
643
+ def draw_property_value(html, value)
644
+ if value.scalar?
645
+ return html.h(value)
646
+ elsif value.is_a?(HtmlOutput)
647
+ return value.to_html(html)
648
+ elsif value.is_a?(Tilia::Xml::XmlSerializable)
649
+ # There's no default html output for this property, we're going
650
+ # to output the actual xml serialization instead.
651
+ xml = @server.xml.write('{DAV:}root', value, @server.base_uri)
652
+ # removing first and last line, as they contain our root
653
+ # element.
654
+ xml = xml.split("\n")
655
+ xml = xml[2, 2]
656
+ return "<pre>#{html.h(xml.join("\n"))}</pre>"
657
+ else
658
+ return '<em>unknown</em>'
659
+ end
660
+ end
661
+
662
+ public
663
+
664
+ # Returns a plugin name.
665
+ #
666
+ # Using this name other plugins will be able to access other plugins
667
+ # using \Sabre\DAV\Server::getPlugin
668
+ #
669
+ # @return string
670
+ def plugin_name
671
+ 'browser'
672
+ end
673
+
674
+ # Returns a bunch of meta-data about the plugin.
675
+ #
676
+ # Providing this information is optional, and is mainly displayed by the
677
+ # Browser plugin.
678
+ #
679
+ # The description key in the returned array may contain html and will not
680
+ # be sanitized.
681
+ #
682
+ # @return array
683
+ def plugin_info
684
+ {
685
+ 'name' => plugin_name,
686
+ 'description' => 'Generates HTML indexes and debug information for your sabre/dav server',
687
+ 'link' => 'http://sabre.io/dav/browser-plugin/'
688
+ }
689
+ end
690
+ end
691
+ end
692
+ end
693
+ end