PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Domains/FossilProxy/FossilProxy.tcl

http://wub.googlecode.com/
TCL | 295 lines | 253 code | 30 blank | 12 comment | 32 complexity | 55b97ac7cc998a338b4ab8378c88f22b MD5 | raw file
  1. package require OO
  2. package require Cookies
  3. package require Query
  4. package require jQ
  5. package provide FossilProxy 1.0
  6. set ::API(Domains/FossilProxy) {
  7. {
  8. A domain to serve as proxy for the 'fossil http' web interface.
  9. }
  10. fossil_dir {Directory where fossil repositories are located. The proxy will work for all repositories in this directory which are named *.fossil, where the basename of the repository is part of the URL. Mandatory.}
  11. fossil_command {Path to fossil command. Default is 'fossil'}
  12. prefix {Path where fossil repositories are mounted in the URL. Mounted in root is the default.}
  13. repositories_list_body {HTML body used to show list of available repositories. %REPOS% is replace by an unordered list of repositories. Default is empty.}
  14. }
  15. oo::class create FossilProxy {
  16. superclass Direct
  17. method strip_prefix { path } {
  18. variable prefix
  19. if {[string length $prefix] && [string match "$prefix*" $path]} {
  20. set path [string range $path [string length $prefix] end]
  21. }
  22. return $path
  23. }
  24. method /direct/user/togglepriv/post {r R U P} {
  25. variable fossil_dir
  26. variable fossil_command
  27. if {[catch {exec $fossil_command user capabilities $U -R [file join $fossil_dir $R]} Res]} {
  28. error $Res
  29. }
  30. set idx [string first $P $Res]
  31. if {$idx >= 0} {
  32. set Res [string replace $Res $idx $idx]
  33. set msg "Revoked $P from user $U for repository $R: $Res"
  34. } else {
  35. append Res $P
  36. set msg "Granted $P to user $U for repository $R: $Res"
  37. }
  38. if {[catch {exec $fossil_command user capabilities $U $Res -R [file join $fossil_dir $R]} Res]} {
  39. error $Res
  40. }
  41. return [Http NoCache [Http Ok $r $msg]]
  42. }
  43. method /direct/user/create/post {r R U C P} {
  44. variable fossil_dir
  45. variable fossil_command
  46. if {[catch {exec $fossil_command user new $U $C $P -R [file join $fossil_dir $R]} Res]} {
  47. error $Res
  48. }
  49. return [Http NoCache [Http Ok $r $Res]]
  50. }
  51. method /direct/user/password/post { r R U P} {
  52. variable fossil_dir
  53. variable fossil_command
  54. if {[catch {exec $fossil_command user password $U $P -R [file join $fossil_dir $R]} Res]} {
  55. error $Res
  56. }
  57. return [Http NoCache [Http Ok $r $Res]]
  58. }
  59. method /direct/privs {r} {
  60. variable prefix
  61. variable fossil_dir
  62. variable fossil_command
  63. set uidl {}
  64. set C ""
  65. set repoid 0
  66. set privid 0
  67. set fnml [lsort -dictionary [glob -nocomplain -tails -dir $fossil_dir *.fossil]]
  68. foreach fnm $fnml {
  69. append C [<a> name repo$repoid [<h2> [file rootname $fnm]]]
  70. set rnm [file join $fossil_dir $fnm]
  71. if {[catch {exec $fossil_command user list -R $rnm} R]} {
  72. error $R
  73. }
  74. unset -nocomplain kprivs
  75. unset -nocomplain privs
  76. unset -nocomplain uidl
  77. foreach l [split $R \n] {
  78. set idx [string first " " $l]
  79. set uid [string trim [string range $l 0 $idx]]
  80. set contact [string trim [string range $l $idx end]]
  81. lappend uidl [list $uid $contact]
  82. if {[catch {exec $fossil_command user capabilities -R $rnm $uid} P]} {
  83. error $P
  84. }
  85. foreach p [split $P {}] {
  86. set kprivs($p) 1
  87. set privs($uid,$p) 1
  88. }
  89. }
  90. set data {}
  91. foreach l $uidl {
  92. lassign $l uid contact
  93. set l [list uid $uid contact $contact]
  94. foreach p [lsort -dictionary [array names kprivs]] {
  95. #lappend l $p "<input id='$privid' type='checkbox' OnClick='FossilProxy.togglepriv($privid, \"$fnm\", \"$uid\", \"$p\")' [expr {[info exists privs($uid,$p)]?"checked":""}]>"
  96. lappend l $p [expr {[info exists privs($uid,$p)]?"X":"-"}]
  97. incr privid
  98. }
  99. lappend data [incr i] $l
  100. append C " </tr>\n"
  101. }
  102. append C [Report html $data headers [list uid contact {*}[lsort -dictionary [array names kprivs]]] class tablesorter sortable 0 evenodd 0 htitle ""]
  103. incr repoid
  104. }
  105. set T [<h1> "Repository privileges"]\n
  106. append T <ul>\n
  107. set repoid 0
  108. foreach fnm $fnml {
  109. append T [<li> [<a> href #repo$repoid [file rootname $fnm]]]\n
  110. incr repoid
  111. }
  112. append T </ul>\n
  113. append T $C
  114. set r [jQ tablesorter $r table]
  115. dict set r -content $T
  116. dict set r content-type x-text/html-fragment
  117. dict set r -title "Repository privileges"
  118. return [Http NoCache [Http Ok $r]]
  119. }
  120. method list_repos {r} {
  121. variable prefix
  122. variable fossil_dir
  123. variable repositories_list_body
  124. set C "<ul>\n"
  125. foreach fnm [lsort -dictionary [glob -nocomplain -tails -dir $fossil_dir *.fossil]] {
  126. append C [<li> [<a> href $prefix/[file rootname $fnm] [file rootname $fnm]]]\n
  127. }
  128. append C "</ul>\n"
  129. return [Http NoCache [Http Ok $r [regsub {%REPOS%} $repositories_list_body $C]]]
  130. }
  131. method fossil_http { r } {
  132. variable fnmid
  133. variable prefix
  134. variable fossil_dir
  135. variable fossil_command
  136. # Construct a HTTP request to send to 'fossil http', strip the prefix as fossil doesn't know about it
  137. if {[dict get $r -method] eq "POST"} {
  138. set fr "POST [my strip_prefix [dict get $r -path]]"
  139. append fr " HTTP/1.1\n"
  140. } else {
  141. lassign [dict get $r -header] meth url ver
  142. set url [my strip_prefix $url]
  143. if {$url in {{} {/}}} {
  144. return [my list_repos $r]
  145. }
  146. set fr "$meth $url $ver\n"
  147. }
  148. # Add headers to request
  149. dict for {k v} $r {
  150. switch -nocase -glob -- $k {
  151. -* {}
  152. default { append fr "$k: $v\n" }
  153. }
  154. }
  155. # Add content to request
  156. if {[dict exists $r -entity]} {
  157. append fr \n[dict get $r -entity]
  158. }
  159. # Use a thread to process the request to avoid blocking on long running calls
  160. return [Httpd Thread {
  161. package require Cookies
  162. package require Dict
  163. set qfnm Q$fnmid
  164. set f [open $qfnm w]
  165. fconfigure $f -encoding binary -translation binary
  166. puts -nonewline $f $fr
  167. close $f
  168. # Call fossil
  169. set fnm R$fnmid
  170. set f [open $fnm w]
  171. fconfigure $f -encoding binary -translation binary
  172. if {[catch {exec $fossil_command http $fossil_dir >@ $f < $qfnm} R]} {
  173. error $R
  174. }
  175. close $f
  176. set f [open $fnm r]
  177. fconfigure $f -encoding binary -translation binary
  178. set R [read $f]
  179. close $f
  180. file delete $qfnm
  181. file delete $fnm
  182. # Extract headers from response
  183. set n 0
  184. set response 404
  185. set location ""
  186. set content_type "test/html"
  187. set content_length -1
  188. set content_found 0
  189. foreach l [split $R \n] {
  190. incr n
  191. if {[string length $l] == 0} {
  192. set content_found 1
  193. break
  194. }
  195. switch -nocase -glob -- $l {
  196. "HTTP/*" {
  197. lassign [split $l] http response
  198. }
  199. "Content-Type:*" {
  200. set content_type [string trim [string range $l 13 end]]
  201. }
  202. "Content-Length:*" {
  203. set content_length [string trim [string range $l 15 end]]
  204. }
  205. "Location:*" {
  206. set location [string trim [string range $l 9 end]]
  207. }
  208. "Set-Cookie:*" {
  209. # Pass on cookies, make sure to fix the path by adding prefix
  210. set cdict [lindex [Cookies parse4client [string trim [string range $l 11 end]]] 1]
  211. set r [Cookies Add $r -path $prefix[dict get? $cdict -Path] -name [dict get? $cdict -name] -value [dict get? $cdict -value] -expires "next month"]
  212. }
  213. }
  214. }
  215. # Extract contents from response
  216. set C ""
  217. if {$content_length >= 0} {
  218. set C [string range $R end-[expr {$content_length-1}] end]
  219. }
  220. # Fix up prefixes if not mounted in /
  221. if {[string length $prefix] && [string match "text/html*" $content_type]} {
  222. regsub -all { href=\"\/} $C " href=\"$prefix/" C
  223. regsub -all { href=\'\/} $C " href='$prefix/" C
  224. regsub -all { src=\"\/} $C " src=\"$prefix/" C
  225. regsub -all { src=\'\/} $C " src='$prefix/" C
  226. }
  227. # Send responses
  228. switch -exact -- $response {
  229. 200 {
  230. return [Http NoCache [Http Ok $r $C $content_type]]
  231. }
  232. 302 {
  233. # Make sure to fix the path by adding prefix
  234. return [Http Redirect $r $prefix$location]
  235. }
  236. 404 {
  237. return [Http NotFound $r]
  238. }
  239. default {
  240. return [Http NoCache [Http Ok $r "Dont know what to do with 'fossil http' response:\n$R"]]
  241. }
  242. }
  243. } r $r fr $fr fossil_dir $fossil_dir fossil_command $fossil_command prefix $prefix fnmid [incr fnmid]]
  244. }
  245. method do {r} {
  246. variable prefix
  247. set path [dict get $r -path]
  248. if {[string match "$prefix/direct/privs*" $path] ||
  249. [string match "$prefix/direct/user*" $path]} {
  250. return [next $r]
  251. } else {
  252. return [my fossil_http $r]
  253. }
  254. }
  255. constructor {args} {
  256. variable fnmid 0
  257. variable prefix ""
  258. variable fossil_command "fossil"
  259. variable {*}[Site var? FossilProxy] {*}$args ;# allow .ini file to modify defaults
  260. if {![info exists fossil_dir]} {
  261. error "fossil_dir not set"
  262. }
  263. catch {next {*}$args}
  264. }
  265. }