SVNWebDAV.Mod 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. MODULE SVNWebDAV; (** AUTHOR "rstoll"; *)
  2. IMPORT
  3. WebHTTP, Files, Strings, Streams, Dates,
  4. XML, XMLObjects,KernelLog,
  5. SVNAdmin, SVNUtil, SVNOutput,
  6. OdSvn, OdXml;
  7. PROCEDURE Checkout* (svn : OdSvn.OdSvn; CONST pathName : ARRAY OF CHAR; CONST workName : ARRAY OF CHAR; VAR res : WORD );
  8. VAR
  9. name, err, vcc, repoUUID, bln, bc, version, workUrl : ARRAY 256 OF CHAR;
  10. props: WebHTTP.AdditionalField;
  11. ver, pos : LONGINT;
  12. BEGIN
  13. svn.checkout := TRUE;
  14. res := SVNOutput.ResOK;
  15. Files.SplitPath ( pathName, workUrl, name );
  16. svn.repositoryPathLength := 999; (* we don't want a update-target in the update request *)
  17. IF Files.Old ( workName ) = NIL THEN
  18. COPY ( pathName, workUrl );
  19. svn.FileUpdate ( FALSE );
  20. ELSE
  21. svn.FileUpdate ( TRUE );
  22. END;
  23. svn.Propfind ( workUrl, "D:version-controlled-configuration.D:resourcetype.D2:baseline-relative-path.D2:repository-uuid", props, err );
  24. IF (svn.pfStatus >= 400) OR (svn.pfStatus = 0) THEN
  25. PrintError ( svn, res );
  26. RETURN;
  27. END;
  28. IF ~WebHTTP.GetAdditionalFieldValue(props, "DAV:version-controlled-configuration", vcc) THEN END;
  29. IF ~WebHTTP.GetAdditionalFieldValue(props, "http://subversion.tigris.org/xmlns/dav/repository-uuid", repoUUID) THEN END;
  30. svn.Propfind ( vcc, "D:checked-in", props, err );
  31. IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:checked-in", bln ) THEN END;
  32. svn.Propfind ( bln, "D:baseline-collection.D:version-name", props, err );
  33. IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:baseline-collection", bc ) THEN END;
  34. IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:version-name", version ) THEN END;
  35. IF err # "" THEN
  36. svn.context.out.String ( err ); svn.context.out.Ln;
  37. RETURN;
  38. END;
  39. Strings.StrToIntPos(version, ver, pos);
  40. svn.UpdateReport ( pathName, vcc, workUrl, -1, ver, workName, res );
  41. svn.checkout := FALSE;
  42. END Checkout;
  43. (* SVN update using .svn directories *)
  44. PROCEDURE Update* (svn : OdSvn.OdSvn; CONST pathName : ARRAY OF CHAR; pathNameVersion : LONGINT; CONST workName : ARRAY OF CHAR; VAR res : WORD );
  45. VAR
  46. name, err, vcc, repoUUID, bln, bc, version, workUrl : ARRAY 256 OF CHAR;
  47. props: WebHTTP.AdditionalField;
  48. ver, pos : LONGINT;
  49. BEGIN
  50. res := SVNOutput.ResOK;
  51. Files.SplitPath ( pathName, workUrl, name );
  52. IF Files.Old ( workName ) = NIL THEN
  53. COPY ( pathName, workUrl );
  54. svn.FileUpdate ( FALSE );
  55. ELSE
  56. svn.FileUpdate ( TRUE );
  57. END;
  58. svn.traverseDummy := TRUE; (* first call *)
  59. SVNAdmin.Traverse ( workName, UpdateHandler, svn, FALSE, res ); (* don't read the version url from wcprops *)
  60. svn.Propfind ( workUrl, "D:version-controlled-configuration.D:resourcetype.D2:baseline-relative-path.D2:repository-uuid", props, err );
  61. IF (svn.pfStatus >= 400) OR (svn.pfStatus = 0) THEN
  62. PrintError ( svn, res );
  63. RETURN;
  64. END;
  65. IF ~WebHTTP.GetAdditionalFieldValue(props, "DAV:version-controlled-configuration", vcc) THEN END;
  66. IF ~WebHTTP.GetAdditionalFieldValue(props, "http://subversion.tigris.org/xmlns/dav/repository-uuid", repoUUID) THEN END;
  67. svn.Propfind ( vcc, "D:checked-in", props, err );
  68. IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:checked-in", bln ) THEN END;
  69. svn.Propfind ( bln, "D:baseline-collection.D:version-name", props, err );
  70. IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:baseline-collection", bc ) THEN END;
  71. IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:version-name", version ) THEN END;
  72. IF err # "" THEN
  73. svn.context.out.String ( err ); svn.context.out.Ln;
  74. RETURN;
  75. END;
  76. Strings.StrToIntPos(version, ver, pos);
  77. svn.UpdateReport ( pathName, vcc, workUrl, pathNameVersion, ver, workName, res );
  78. END Update;
  79. PROCEDURE Commit* ( svn : OdSvn.OdSvn; CONST pathName, workName, message : ARRAY OF CHAR; VAR res : WORD );
  80. VAR
  81. act: OdSvn.Activity;
  82. uuid : Strings.String;
  83. name, err, vcc, bln, workUrl, wbl : ARRAY 256 OF CHAR;
  84. props, patch : WebHTTP.AdditionalField;
  85. resHeader: WebHTTP.ResponseHeader;
  86. BEGIN
  87. uuid := SVNUtil.GetUUID();
  88. NEW ( act, svn.client, uuid^ );
  89. ASSERT ( act # NIL );
  90. act.make;
  91. IF act.getUrl() = NIL THEN
  92. svn.pfStatus := 0;
  93. PrintError ( svn, res );
  94. RETURN;
  95. END;
  96. Files.SplitPath ( pathName, workUrl, name );
  97. IF Files.Old ( workName ) = NIL THEN
  98. COPY ( pathName, workUrl );
  99. END;
  100. svn.Propfind ( workUrl, "D:version-controlled-configuration", props, err );
  101. IF ~WebHTTP.GetAdditionalFieldValue(props, "DAV:version-controlled-configuration", vcc) THEN END;
  102. IF (svn.pfStatus >= 400) OR (svn.pfStatus = 0) THEN
  103. PrintError ( svn, res );
  104. RETURN;
  105. END;
  106. svn.Propfind ( vcc, "D:checked-in", props, err );
  107. IF ~WebHTTP.GetAdditionalFieldValue ( props, "DAV:checked-in", bln ) THEN END;
  108. svn.Checkout ( bln, resHeader, err );
  109. COPY ( resHeader.location, wbl );
  110. WebHTTP.SetAdditionalFieldValue ( patch, "log xmlns=http://subversion.tigris.org/xmlns/svn/", message );
  111. svn.Proppatch ( wbl, patch, err );
  112. svn.Propfind ( workUrl, "D:checked-in", props, err );
  113. IF ~WebHTTP.GetAdditionalFieldValue(props, "DAV:checked-in", svn.ver) THEN END;
  114. svn.Checkout ( svn.ver, resHeader, err );
  115. COPY ( resHeader.location, svn.wrk );
  116. svn.removeDir := FALSE;
  117. svn.countChanges := 0;
  118. SVNAdmin.Traverse ( workName, CommitHandler, svn, TRUE, res );
  119. (* don't merge if there are no modified files *)
  120. IF svn.countChanges > 0 THEN
  121. svn.Merge ( workUrl, act.getUrl(), resHeader, err );
  122. IF resHeader.statuscode = 200 THEN
  123. ParseMergeContent ( svn, workUrl, workName );
  124. END;
  125. END;
  126. act.delete;
  127. END Commit;
  128. PROCEDURE ParseMergeContent ( svn : OdSvn.OdSvn; CONST baseUrl, basePath : ARRAY OF CHAR );
  129. VAR
  130. root, e, e2 : XML.Element;
  131. enum : XMLObjects.Enumerator;
  132. str, md5 : Strings.String;
  133. p : ANY;
  134. xml : OdXml.OdXml;
  135. vcc, vurl, ver, tmp, tmp2, path, name, date : ARRAY 256 OF CHAR;
  136. creationdate, creator, status : ARRAY 33 OF CHAR;
  137. version : ARRAY 10 OF CHAR;
  138. len, ft,fd : LONGINT; res: WORD;
  139. adminDir : SVNAdmin.Entry;
  140. f : Files.File;
  141. BEGIN
  142. NEW ( xml );
  143. NEW ( adminDir, NIL );
  144. root := svn.resultDoc.GetRoot();
  145. str := root.GetName();
  146. IF str^ # "D:merge-response" THEN RETURN END;
  147. enum := root.GetContents();
  148. IF ~enum.HasMoreElements() THEN RETURN END;
  149. p := enum.GetNext();
  150. e := p ( XML.Element );
  151. str := e.GetName();
  152. IF str^ # "D:updated-set" THEN RETURN END;
  153. enum := e.GetContents();
  154. IF ~enum.HasMoreElements() THEN RETURN END;
  155. p := enum.GetNext();
  156. e := p ( XML.Element );
  157. str := e.GetName();
  158. IF str^ # "D:response" THEN RETURN END;
  159. e2 := xml.SplitElement ( e, "DAV:href" );
  160. ASSERT ( e2 # NIL );
  161. OdXml.GetCharData ( e2, vcc );
  162. e2 := xml.SplitElement ( e, "DAV:propstat.DAV:prop.DAV:creationdate" ); ASSERT ( e2 # NIL );
  163. OdXml.GetCharData ( e2, creationdate );
  164. e2 := xml.SplitElement ( e, "DAV:propstat.DAV:prop.DAV:version-name" ); ASSERT ( e2 # NIL );
  165. OdXml.GetCharData ( e2, version );
  166. e2 := xml.SplitElement ( e, "DAV:propstat.DAV:prop.DAV:creator-displayname" ); ASSERT ( e2 # NIL );
  167. OdXml.GetCharData ( e2, creator );
  168. len := Strings.Length ( baseUrl );
  169. ASSERT ( ~Strings.EndsWith ( "/", basePath ) );
  170. WHILE enum.HasMoreElements() DO
  171. p := enum.GetNext();
  172. IF p IS XML.Element THEN
  173. e := p ( XML.Element );
  174. str := e.GetName();
  175. e2 := xml.SplitElement ( e, "DAV:href" ); ASSERT ( e2 # NIL );
  176. OdXml.GetCharData ( e2, vurl );
  177. (*e2 := xml.SplitElement ( e, "DAV:propstat.DAV:prop.DAV:resourcetype.DAV:collection" );
  178. IF e2 = NIL THEN
  179. (*KernelLog.String ( "NIL collection" );*)
  180. ELSE
  181. (*KernelLog.String ( "NOT NIL collection" );*)
  182. END;*)
  183. e2 := xml.SplitElement ( e, "DAV:propstat.DAV:prop.DAV:checked-in.DAV:href" ); ASSERT ( e2 # NIL );
  184. OdXml.GetCharData ( e2, ver );
  185. e2 := xml.SplitElement ( e, "DAV:propstat.DAV:status" ); ASSERT ( e2 # NIL );
  186. OdXml.GetCharData ( e2, status );
  187. IF Strings.Match ( "HTTP/1.? 200 OK", status ) THEN
  188. str := Strings.Substring2 ( len, vurl );
  189. SVNUtil.UrlDecode ( str^, tmp2 );
  190. Strings.Concat ( basePath, tmp2, tmp );
  191. adminDir.SetPath ( tmp, res ); ASSERT ( res = SVNOutput.ResOK );
  192. adminDir.CreateTempfile;
  193. f := Files.Old ( tmp );
  194. IF f # NIL THEN
  195. Files.SplitPath ( tmp, path, name );
  196. IF adminDir.IsItemVersioned ( name ) THEN
  197. adminDir.ReadWriteLines ( 1 );
  198. adminDir.ReadWriteString ( version );
  199. adminDir.ReadWriteLines ( 2 );
  200. adminDir.ReadWriteString ( "" ); (* remove scheduled stuff *)
  201. adminDir.ReadWriteString ( creationdate );
  202. md5 := SVNUtil.GetChecksum ( tmp );
  203. adminDir.ReadWriteString ( md5^ );
  204. f.GetDate ( ft, fd );
  205. Strings.FormatDateTime ( SVNOutput.DateFormat, Dates.OberonToDateTime ( fd, ft ), date );
  206. adminDir.ReadWriteString ( date );
  207. adminDir.ReadWriteString ( version );
  208. adminDir.ReadWriteString ( creator );
  209. adminDir.ReadWriteRest;
  210. adminDir.WriteUpdate;
  211. SVNAdmin.CopyToBaseFile ( tmp );
  212. SVNAdmin.WriteWCPROPS ( path, name, ver );
  213. ELSE
  214. svn.context.out.String ( "ERROR: received merge request, but item is not versioned" );
  215. svn.context.out.Ln;
  216. END;
  217. ELSE
  218. adminDir.ReadWriteLines ( 3 );
  219. adminDir.ReadWriteString ( version );
  220. adminDir.ReadWriteLines ( 2 );
  221. adminDir.ReadWriteString ( "" ); (* remove scheduled stuff *)
  222. adminDir.ReadWriteLines ( 2 );
  223. adminDir.ReadWriteString ( creationdate ); (* correct? *)
  224. adminDir.ReadWriteString ( version );
  225. adminDir.ReadWriteString ( creator );
  226. (* remove schedule entries *)
  227. adminDir.ReadWriteToEOE;
  228. WHILE ~adminDir.IsEOF () DO
  229. adminDir.ReadWriteLines ( 1 );
  230. adminDir.ReadWriteLine ( tmp2 );
  231. IF tmp2 = "dir" THEN
  232. adminDir.ReadWriteEOE;
  233. ELSE
  234. adminDir.ReadWriteToEOE;
  235. END;
  236. END;
  237. adminDir.WriteUpdate;
  238. SVNAdmin.WriteWCPROPS ( tmp, "", ver );
  239. END;
  240. END;
  241. END;
  242. END;
  243. END ParseMergeContent;
  244. PROCEDURE UpdateHandler* ( CONST path : ARRAY OF CHAR; entry : SVNAdmin.EntryEntity; data : ANY ) : BOOLEAN;
  245. VAR
  246. svn : OdSvn.OdSvn;
  247. BEGIN
  248. (* TODO:
  249. - search for files/directories which are on a separate revision
  250. - search for missing resources..and restore them:
  251. - dirs: neuer request!
  252. - files: restore von base copy
  253. *)
  254. (* KernelLog.String ( "path: " );
  255. KernelLog.String ( path );
  256. KernelLog.String ( "//" );
  257. KernelLog.String ( entry.Name );
  258. KernelLog.Ln;*)
  259. svn := data ( OdSvn.OdSvn );
  260. IF svn.traverseDummy THEN
  261. svn.traverseDummy := FALSE;
  262. svn.repositoryPathLength := Strings.Length ( entry.RepositoryRoot ) - Strings.IndexOfByte ( '/', 7, entry.RepositoryRoot );
  263. END;
  264. RETURN TRUE;
  265. END UpdateHandler;
  266. PROCEDURE CommitHandler* ( CONST path : ARRAY OF CHAR; entry : SVNAdmin.EntryEntity; data : ANY ) : BOOLEAN;
  267. VAR
  268. str : Strings.String;
  269. err, wrk, tmp2 : ARRAY 256 OF CHAR;
  270. props: WebHTTP.AdditionalField;
  271. svn : OdSvn.OdSvn;
  272. res : WORD;
  273. resHeader: WebHTTP.ResponseHeader;
  274. m : SVNOutput.Message;
  275. BEGIN
  276. svn := data ( OdSvn.OdSvn );
  277. NEW ( str, 256 );
  278. str := Strings.Substring2 ( Strings.Length ( entry.RepositoryRoot ), entry.Url );
  279. IF str^[0] = Files.PathDelimiter THEN str := Strings.Substring2 ( 1, str^ ) END;
  280. IF ~svn.removeDir THEN
  281. svn.removeDir := TRUE;
  282. Strings.Truncate ( svn.wrk, Strings.Length(svn.wrk) - Strings.Length(str^) );
  283. END;
  284. Strings.Concat ( svn.wrk, str^, tmp2 );
  285. SVNUtil.UrlEncode ( tmp2, wrk );
  286. IF entry.NodeKind = "file" THEN
  287. Files.JoinPath ( path, entry.Name, tmp2 );
  288. IF entry.Schedule = "add" THEN (* DONE *)
  289. svn.Propfind ( wrk, "D:version-controlled-configuration.D2:repository-uuid", props, err );
  290. IF svn.pfStatus = 404 THEN (* 404 = not found *)
  291. svn.context.out.String ( "Adding " ); svn.context.out.String ( tmp2 ); svn.context.out.Ln;
  292. Put ( wrk, tmp2, svn, res );
  293. ExpectedResult ( 201, svn, wrk, tmp2, "add file" ); (* 201 = created *)
  294. ELSE
  295. svn.context.out.String ( " ERROR: " ); svn.context.out.String ( wrk ); svn.context.out.String ( " is already on the server!" ); svn.context.out.Ln;
  296. svn.countChanges := 0; (* abort commit *)
  297. RETURN FALSE;
  298. END;
  299. ELSIF entry.Schedule = "delete" THEN
  300. (* TODO: remove entry in entries file and all-wcprops file: there is no report packet !!! *)
  301. (*IF ~entry.GlobalRemoval THEN (* file will be deleted anyway if the top directory is removed *)
  302. svn.context.out.String ( "Deleting " ); svn.context.out.String ( tmp2 ); svn.context.out.Ln;
  303. Delete ( wrk, svn, res );
  304. ExpectedResult ( 204, svn, wrk, tmp2, "delete file" ); (* 204 = no content *)
  305. END;*)
  306. ELSE (* DONE *)
  307. IF ~SVNUtil.CheckChecksum ( tmp2, entry.Checksum ) THEN
  308. svn.context.out.String ( "Sending " );
  309. svn.context.out.String ( tmp2 );
  310. svn.context.out.Ln;
  311. svn.Checkout ( entry.VersionUrl, resHeader, err );
  312. IF resHeader.statuscode >= 400 THEN
  313. NEW ( m, svn.context );
  314. IF resHeader.statuscode = 409 THEN
  315. m.Print ( SVNOutput.ResCOMMITOUTOFDATE, tmp2 );
  316. ELSE
  317. svn.context.out.String ( "HTTP error! Statuscode: " ); svn.context.out.Int ( resHeader.statuscode, 0 );
  318. svn.context.out.Ln;
  319. m.Print ( SVNOutput.ResCOMMITUNSPECIFIED, tmp2 );
  320. END;
  321. svn.countChanges := 0; (* abort commit *)
  322. RETURN FALSE;
  323. ELSE
  324. Put ( wrk, tmp2, svn, res );
  325. ExpectedResult ( 204, svn, wrk, tmp2, "add file" ); (* 204 = no content *)
  326. END;
  327. END;
  328. END;
  329. ELSE
  330. IF entry.Schedule = "add" THEN (* DONE *)
  331. svn.context.out.String ( "Adding " ); svn.context.out.String ( path ); svn.context.out.Ln;
  332. Mkcol ( wrk, svn, res );
  333. ExpectedResult ( 201, svn, wrk, tmp2, "add directory" );
  334. ELSIF entry.Schedule = "delete" THEN
  335. (* TODO: remove entry in entries file *)
  336. (*svn.context.out.String ( "Deleting " ); svn.context.out.String ( tmp2 ); svn.context.out.Ln;
  337. Delete ( wrk, svn, res );
  338. ExpectedResult ( 204, svn, wrk, tmp2, "delete file" ); (* 204 = no content *)
  339. *)
  340. ELSE
  341. (* do nothing *)
  342. END;
  343. END;
  344. RETURN TRUE;
  345. END CommitHandler;
  346. PROCEDURE ExpectedResult ( status : LONGINT; svn : OdSvn.OdSvn; CONST wrk, lcl, message : ARRAY OF CHAR );
  347. BEGIN
  348. IF svn.pfStatus # status THEN
  349. svn.context.out.String ( "ERROR: failed to " );
  350. svn.context.out.String ( message );
  351. svn.context.out.String ( ": " );
  352. svn.context.out.String ( lcl );
  353. svn.context.out.Ln;
  354. svn.context.out.String ( "url: " );
  355. svn.context.out.String ( wrk );
  356. svn.context.out.Ln;
  357. ELSE
  358. INC ( svn.countChanges );
  359. END;
  360. END ExpectedResult;
  361. PROCEDURE Mkcol* ( CONST url : ARRAY OF CHAR; svn : OdSvn.OdSvn; VAR res : WORD );
  362. VAR
  363. resHeader: WebHTTP.ResponseHeader;
  364. out : Streams.Reader;
  365. BEGIN
  366. svn.client.Mkcol ( url, resHeader, out, res );
  367. svn.pfStatus := resHeader.statuscode;
  368. END Mkcol;
  369. PROCEDURE Delete* ( CONST url : ARRAY OF CHAR; svn : OdSvn.OdSvn; VAR res : WORD );
  370. VAR
  371. resHeader: WebHTTP.ResponseHeader;
  372. out : Streams.Reader;
  373. BEGIN
  374. svn.client.Delete ( url, resHeader, out, res );
  375. svn.pfStatus := resHeader.statuscode;
  376. END Delete;
  377. (* sends 'workName' to the svn.wrk address *)
  378. PROCEDURE Put* ( CONST workUrl, workName: ARRAY OF CHAR; svn : OdSvn.OdSvn; VAR res : WORD );
  379. VAR
  380. f : Files.File;
  381. resHeader: WebHTTP.ResponseHeader;
  382. reqHeader: WebHTTP.RequestHeader;
  383. in : Files.Reader;
  384. out : Streams.Reader;
  385. lenStr: ARRAY 10 OF CHAR;
  386. m : SVNOutput.Message;
  387. BEGIN
  388. f := Files.Old(workName);
  389. IF f # NIL THEN
  390. Files.OpenReader ( in, f, 0 );
  391. WebHTTP.SetAdditionalFieldValue ( reqHeader.additionalFields, "Content-Type", "application/octet-stream" );
  392. Strings.IntToStr ( f.Length(), lenStr );
  393. WebHTTP.SetAdditionalFieldValue ( reqHeader.additionalFields, "Content-Length", lenStr );
  394. svn.client.Put ( workUrl, reqHeader, resHeader, out, in, res );
  395. svn.pfStatus := resHeader.statuscode;
  396. ELSE
  397. svn.context.out.String ( " ERROR: PUT " );
  398. NEW ( m, svn.context );
  399. m.Print ( SVNOutput.ResFILENOTFOUND, workName );
  400. END;
  401. END Put;
  402. PROCEDURE PrintError ( svn : OdSvn.OdSvn; VAR res : WORD );
  403. BEGIN
  404. svn.context.out.String ( "Server response: " ); svn.context.out.Int ( svn.pfStatus, 0 ); svn.context.out.Ln;
  405. IF svn.pfStatus = 401 THEN
  406. res := SVNOutput.ResNOTAUTHORIZED;
  407. ELSE
  408. res := SVNOutput.ResUNEXPECTEDSERVERRESPONSE;
  409. END;
  410. END PrintError;
  411. END SVNWebDAV.