agi.zip.go 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570
  1. package agi
  2. import (
  3. "archive/zip"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "github.com/bodgit/sevenzip"
  12. "github.com/mholt/archiver/v3"
  13. "github.com/robertkrimen/otto"
  14. "imuslab.com/arozos/mod/agi/static"
  15. "imuslab.com/arozos/mod/filesystem"
  16. "imuslab.com/arozos/mod/filesystem/arozfs"
  17. "imuslab.com/arozos/mod/info/logger"
  18. )
  19. /*
  20. AGI Zip File View or Extract Library
  21. This library provide agi API for apps that want to view or extract agi zip files.
  22. Author: tobychui
  23. */
  24. func (g *Gateway) ZipLibRegister() {
  25. err := g.RegisterLib("ziplib", g.injectZipFileLibFunctions)
  26. if err != nil {
  27. logger.PrintAndLog("Agi", fmt.Sprint(err), nil)
  28. os.Exit(1)
  29. }
  30. }
  31. func (g *Gateway) injectZipFileLibFunctions(payload *static.AgiLibInjectionPayload) {
  32. vm := payload.VM
  33. u := payload.User
  34. scriptFsh := payload.ScriptFsh
  35. // extractZipFile(sourceVpath, destVpath) => Extract zip file to destination
  36. vm.Set("_ziplib_extractZipFile", func(call otto.FunctionCall) otto.Value {
  37. srcVpath, err := call.Argument(0).ToString()
  38. if err != nil {
  39. g.RaiseError(err)
  40. return otto.FalseValue()
  41. }
  42. destVpath, err := call.Argument(1).ToString()
  43. if err != nil {
  44. g.RaiseError(err)
  45. return otto.FalseValue()
  46. }
  47. // Rewrite paths if relative
  48. srcVpath = static.RelativeVpathRewrite(scriptFsh, srcVpath, vm, u)
  49. destVpath = static.RelativeVpathRewrite(scriptFsh, destVpath, vm, u)
  50. // Check permissions
  51. if !u.CanRead(srcVpath) {
  52. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+srcVpath))
  53. }
  54. if !u.CanWrite(destVpath) {
  55. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+destVpath))
  56. }
  57. // Get real paths
  58. _, srcRpath, err := static.VirtualPathToRealPath(srcVpath, u)
  59. if err != nil {
  60. g.RaiseError(err)
  61. return otto.FalseValue()
  62. }
  63. destFsh, destRpath, err := static.VirtualPathToRealPath(destVpath, u)
  64. if err != nil {
  65. g.RaiseError(err)
  66. return otto.FalseValue()
  67. }
  68. // Extract using archiver
  69. z := archiver.Zip{}
  70. err = z.Unarchive(srcRpath, destRpath)
  71. if err != nil {
  72. g.RaiseError(err)
  73. return otto.FalseValue()
  74. }
  75. // Update ownership
  76. destFsh.FileSystemAbstraction.Walk(destRpath, func(path string, info os.FileInfo, err error) error {
  77. if err == nil && !info.IsDir() {
  78. vp, _ := static.RealpathToVirtualpath(destFsh, path, u)
  79. u.SetOwnerOfFile(destFsh, vp)
  80. }
  81. return nil
  82. })
  83. reply, _ := vm.ToValue(true)
  84. return reply
  85. })
  86. // createZipFile(sourceVpaths, outputVpath) => Create zip file from array of sources
  87. vm.Set("_ziplib_createZipFile", func(call otto.FunctionCall) otto.Value {
  88. sourcesObj, err := call.Argument(0).Export()
  89. if err != nil {
  90. g.RaiseError(err)
  91. return otto.FalseValue()
  92. }
  93. outputVpath, err := call.Argument(1).ToString()
  94. if err != nil {
  95. g.RaiseError(err)
  96. return otto.FalseValue()
  97. }
  98. // Convert sources to string array
  99. var sources []string
  100. switch v := sourcesObj.(type) {
  101. case []interface{}:
  102. for _, s := range v {
  103. if str, ok := s.(string); ok {
  104. sources = append(sources, static.RelativeVpathRewrite(scriptFsh, str, vm, u))
  105. }
  106. }
  107. case string:
  108. sources = append(sources, static.RelativeVpathRewrite(scriptFsh, v, vm, u))
  109. default:
  110. g.RaiseError(errors.New("invalid source format"))
  111. return otto.FalseValue()
  112. }
  113. outputVpath = static.RelativeVpathRewrite(scriptFsh, outputVpath, vm, u)
  114. // Check permissions
  115. for _, src := range sources {
  116. if !u.CanRead(src) {
  117. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+src))
  118. }
  119. }
  120. if !u.CanWrite(outputVpath) {
  121. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+outputVpath))
  122. }
  123. // Get real paths
  124. var sourceRpaths []string
  125. var sourceFshs []*filesystem.FileSystemHandler
  126. for _, src := range sources {
  127. fsh, rpath, err := static.VirtualPathToRealPath(src, u)
  128. if err != nil {
  129. g.RaiseError(err)
  130. return otto.FalseValue()
  131. }
  132. sourceFshs = append(sourceFshs, fsh)
  133. sourceRpaths = append(sourceRpaths, rpath)
  134. }
  135. outputFsh, outputRpath, err := static.VirtualPathToRealPath(outputVpath, u)
  136. if err != nil {
  137. g.RaiseError(err)
  138. return otto.FalseValue()
  139. }
  140. // Create zip file
  141. err = filesystem.ArozZipFile(sourceFshs, sourceRpaths, outputFsh, outputRpath, false)
  142. if err != nil {
  143. g.RaiseError(err)
  144. return otto.FalseValue()
  145. }
  146. // Set ownership
  147. u.SetOwnerOfFile(outputFsh, outputVpath)
  148. reply, _ := vm.ToValue(true)
  149. return reply
  150. })
  151. // createTarFile(sourceVpaths, outputVpath) => Create tar file
  152. vm.Set("_ziplib_createTarFile", func(call otto.FunctionCall) otto.Value {
  153. sourcesObj, err := call.Argument(0).Export()
  154. if err != nil {
  155. g.RaiseError(err)
  156. return otto.FalseValue()
  157. }
  158. outputVpath, err := call.Argument(1).ToString()
  159. if err != nil {
  160. g.RaiseError(err)
  161. return otto.FalseValue()
  162. }
  163. var sources []string
  164. switch v := sourcesObj.(type) {
  165. case []interface{}:
  166. for _, s := range v {
  167. if str, ok := s.(string); ok {
  168. sources = append(sources, static.RelativeVpathRewrite(scriptFsh, str, vm, u))
  169. }
  170. }
  171. case string:
  172. sources = append(sources, static.RelativeVpathRewrite(scriptFsh, v, vm, u))
  173. }
  174. outputVpath = static.RelativeVpathRewrite(scriptFsh, outputVpath, vm, u)
  175. for _, src := range sources {
  176. if !u.CanRead(src) {
  177. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+src))
  178. }
  179. }
  180. if !u.CanWrite(outputVpath) {
  181. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+outputVpath))
  182. }
  183. var sourceRpaths []string
  184. for _, src := range sources {
  185. _, rpath, err := static.VirtualPathToRealPath(src, u)
  186. if err != nil {
  187. g.RaiseError(err)
  188. return otto.FalseValue()
  189. }
  190. sourceRpaths = append(sourceRpaths, rpath)
  191. }
  192. outputFsh, outputRpath, err := static.VirtualPathToRealPath(outputVpath, u)
  193. if err != nil {
  194. g.RaiseError(err)
  195. return otto.FalseValue()
  196. }
  197. tar := archiver.Tar{}
  198. err = tar.Archive(sourceRpaths, outputRpath)
  199. if err != nil {
  200. g.RaiseError(err)
  201. return otto.FalseValue()
  202. }
  203. u.SetOwnerOfFile(outputFsh, outputVpath)
  204. reply, _ := vm.ToValue(true)
  205. return reply
  206. })
  207. // extractTarFile(sourceVpath, destVpath) => Extract tar file
  208. vm.Set("_ziplib_extractTarFile", func(call otto.FunctionCall) otto.Value {
  209. srcVpath, err := call.Argument(0).ToString()
  210. if err != nil {
  211. g.RaiseError(err)
  212. return otto.FalseValue()
  213. }
  214. destVpath, err := call.Argument(1).ToString()
  215. if err != nil {
  216. g.RaiseError(err)
  217. return otto.FalseValue()
  218. }
  219. srcVpath = static.RelativeVpathRewrite(scriptFsh, srcVpath, vm, u)
  220. destVpath = static.RelativeVpathRewrite(scriptFsh, destVpath, vm, u)
  221. if !u.CanRead(srcVpath) {
  222. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+srcVpath))
  223. }
  224. if !u.CanWrite(destVpath) {
  225. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+destVpath))
  226. }
  227. _, srcRpath, err := static.VirtualPathToRealPath(srcVpath, u)
  228. if err != nil {
  229. g.RaiseError(err)
  230. return otto.FalseValue()
  231. }
  232. destFsh, destRpath, err := static.VirtualPathToRealPath(destVpath, u)
  233. if err != nil {
  234. g.RaiseError(err)
  235. return otto.FalseValue()
  236. }
  237. tar := archiver.Tar{}
  238. err = tar.Unarchive(srcRpath, destRpath)
  239. if err != nil {
  240. g.RaiseError(err)
  241. return otto.FalseValue()
  242. }
  243. // Update ownership
  244. destFsh.FileSystemAbstraction.Walk(destRpath, func(path string, info os.FileInfo, err error) error {
  245. if err == nil && !info.IsDir() {
  246. vp, _ := static.RealpathToVirtualpath(destFsh, path, u)
  247. u.SetOwnerOfFile(destFsh, vp)
  248. }
  249. return nil
  250. })
  251. reply, _ := vm.ToValue(true)
  252. return reply
  253. })
  254. // createTarGzFile(sourceVpaths, outputVpath) => Create tar.gz file
  255. vm.Set("_ziplib_createTarGzFile", func(call otto.FunctionCall) otto.Value {
  256. sourcesObj, err := call.Argument(0).Export()
  257. if err != nil {
  258. g.RaiseError(err)
  259. return otto.FalseValue()
  260. }
  261. outputVpath, err := call.Argument(1).ToString()
  262. if err != nil {
  263. g.RaiseError(err)
  264. return otto.FalseValue()
  265. }
  266. var sources []string
  267. switch v := sourcesObj.(type) {
  268. case []interface{}:
  269. for _, s := range v {
  270. if str, ok := s.(string); ok {
  271. sources = append(sources, static.RelativeVpathRewrite(scriptFsh, str, vm, u))
  272. }
  273. }
  274. case string:
  275. sources = append(sources, static.RelativeVpathRewrite(scriptFsh, v, vm, u))
  276. }
  277. outputVpath = static.RelativeVpathRewrite(scriptFsh, outputVpath, vm, u)
  278. for _, src := range sources {
  279. if !u.CanRead(src) {
  280. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+src))
  281. }
  282. }
  283. if !u.CanWrite(outputVpath) {
  284. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+outputVpath))
  285. }
  286. var sourceRpaths []string
  287. for _, src := range sources {
  288. _, rpath, err := static.VirtualPathToRealPath(src, u)
  289. if err != nil {
  290. g.RaiseError(err)
  291. return otto.FalseValue()
  292. }
  293. sourceRpaths = append(sourceRpaths, rpath)
  294. }
  295. outputFsh, outputRpath, err := static.VirtualPathToRealPath(outputVpath, u)
  296. if err != nil {
  297. g.RaiseError(err)
  298. return otto.FalseValue()
  299. }
  300. tgz := archiver.TarGz{}
  301. err = tgz.Archive(sourceRpaths, outputRpath)
  302. if err != nil {
  303. g.RaiseError(err)
  304. return otto.FalseValue()
  305. }
  306. u.SetOwnerOfFile(outputFsh, outputVpath)
  307. reply, _ := vm.ToValue(true)
  308. return reply
  309. })
  310. // extractTarGzFile(sourceVpath, destVpath) => Extract tar.gz file
  311. vm.Set("_ziplib_extractTarGzFile", func(call otto.FunctionCall) otto.Value {
  312. srcVpath, err := call.Argument(0).ToString()
  313. if err != nil {
  314. g.RaiseError(err)
  315. return otto.FalseValue()
  316. }
  317. destVpath, err := call.Argument(1).ToString()
  318. if err != nil {
  319. g.RaiseError(err)
  320. return otto.FalseValue()
  321. }
  322. srcVpath = static.RelativeVpathRewrite(scriptFsh, srcVpath, vm, u)
  323. destVpath = static.RelativeVpathRewrite(scriptFsh, destVpath, vm, u)
  324. if !u.CanRead(srcVpath) {
  325. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+srcVpath))
  326. }
  327. if !u.CanWrite(destVpath) {
  328. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+destVpath))
  329. }
  330. _, srcRpath, err := static.VirtualPathToRealPath(srcVpath, u)
  331. if err != nil {
  332. g.RaiseError(err)
  333. return otto.FalseValue()
  334. }
  335. destFsh, destRpath, err := static.VirtualPathToRealPath(destVpath, u)
  336. if err != nil {
  337. g.RaiseError(err)
  338. return otto.FalseValue()
  339. }
  340. tgz := archiver.TarGz{}
  341. err = tgz.Unarchive(srcRpath, destRpath)
  342. if err != nil {
  343. g.RaiseError(err)
  344. return otto.FalseValue()
  345. }
  346. // Update ownership
  347. destFsh.FileSystemAbstraction.Walk(destRpath, func(path string, info os.FileInfo, err error) error {
  348. if err == nil && !info.IsDir() {
  349. vp, _ := static.RealpathToVirtualpath(destFsh, path, u)
  350. u.SetOwnerOfFile(destFsh, vp)
  351. }
  352. return nil
  353. })
  354. reply, _ := vm.ToValue(true)
  355. return reply
  356. })
  357. // createGzFile(sourceVpath, outputVpath) => Create gz file (single file compression)
  358. vm.Set("_ziplib_createGzFile", func(call otto.FunctionCall) otto.Value {
  359. srcVpath, err := call.Argument(0).ToString()
  360. if err != nil {
  361. g.RaiseError(err)
  362. return otto.FalseValue()
  363. }
  364. outputVpath, err := call.Argument(1).ToString()
  365. if err != nil {
  366. g.RaiseError(err)
  367. return otto.FalseValue()
  368. }
  369. srcVpath = static.RelativeVpathRewrite(scriptFsh, srcVpath, vm, u)
  370. outputVpath = static.RelativeVpathRewrite(scriptFsh, outputVpath, vm, u)
  371. if !u.CanRead(srcVpath) {
  372. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+srcVpath))
  373. }
  374. if !u.CanWrite(outputVpath) {
  375. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+outputVpath))
  376. }
  377. srcFsh, srcRpath, err := static.VirtualPathToRealPath(srcVpath, u)
  378. if err != nil {
  379. g.RaiseError(err)
  380. return otto.FalseValue()
  381. }
  382. outputFsh, outputRpath, err := static.VirtualPathToRealPath(outputVpath, u)
  383. if err != nil {
  384. g.RaiseError(err)
  385. return otto.FalseValue()
  386. }
  387. // Compress using Gz - open source file and create output file
  388. srcFile, err := srcFsh.FileSystemAbstraction.ReadStream(srcRpath)
  389. if err != nil {
  390. g.RaiseError(err)
  391. return otto.FalseValue()
  392. }
  393. defer srcFile.Close()
  394. outFile, err := outputFsh.FileSystemAbstraction.Create(outputRpath)
  395. if err != nil {
  396. g.RaiseError(err)
  397. return otto.FalseValue()
  398. }
  399. defer outFile.Close()
  400. gz := archiver.Gz{}
  401. err = gz.Compress(srcFile, outFile)
  402. if err != nil {
  403. g.RaiseError(err)
  404. return otto.FalseValue()
  405. }
  406. u.SetOwnerOfFile(outputFsh, outputVpath)
  407. reply, _ := vm.ToValue(true)
  408. return reply
  409. })
  410. // extractGzFile(sourceVpath, destVpath) => Extract gz file
  411. vm.Set("_ziplib_extractGzFile", func(call otto.FunctionCall) otto.Value {
  412. srcVpath, err := call.Argument(0).ToString()
  413. if err != nil {
  414. g.RaiseError(err)
  415. return otto.FalseValue()
  416. }
  417. destVpath, err := call.Argument(1).ToString()
  418. if err != nil {
  419. g.RaiseError(err)
  420. return otto.FalseValue()
  421. }
  422. srcVpath = static.RelativeVpathRewrite(scriptFsh, srcVpath, vm, u)
  423. destVpath = static.RelativeVpathRewrite(scriptFsh, destVpath, vm, u)
  424. if !u.CanRead(srcVpath) {
  425. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+srcVpath))
  426. }
  427. if !u.CanWrite(destVpath) {
  428. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+destVpath))
  429. }
  430. srcFsh, srcRpath, err := static.VirtualPathToRealPath(srcVpath, u)
  431. if err != nil {
  432. g.RaiseError(err)
  433. return otto.FalseValue()
  434. }
  435. destFsh, destRpath, err := static.VirtualPathToRealPath(destVpath, u)
  436. if err != nil {
  437. g.RaiseError(err)
  438. return otto.FalseValue()
  439. }
  440. // Decompress using Gz - open source file and create output file
  441. srcFile, err := srcFsh.FileSystemAbstraction.ReadStream(srcRpath)
  442. if err != nil {
  443. g.RaiseError(err)
  444. return otto.FalseValue()
  445. }
  446. defer srcFile.Close()
  447. outFile, err := destFsh.FileSystemAbstraction.Create(destRpath)
  448. if err != nil {
  449. g.RaiseError(err)
  450. return otto.FalseValue()
  451. }
  452. defer outFile.Close()
  453. gz := archiver.Gz{}
  454. err = gz.Decompress(srcFile, outFile)
  455. if err != nil {
  456. g.RaiseError(err)
  457. return otto.FalseValue()
  458. }
  459. u.SetOwnerOfFile(destFsh, destVpath)
  460. reply, _ := vm.ToValue(true)
  461. return reply
  462. })
  463. // isValidZipFile(vpath) => Check if file is a valid archive (zip, tar, tar.gz, gz, etc.)
  464. vm.Set("_ziplib_isValidZipFile", func(call otto.FunctionCall) otto.Value {
  465. vpath, err := call.Argument(0).ToString()
  466. if err != nil {
  467. g.RaiseError(err)
  468. return otto.FalseValue()
  469. }
  470. vpath = static.RelativeVpathRewrite(scriptFsh, vpath, vm, u)
  471. if !u.CanRead(vpath) {
  472. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+vpath))
  473. }
  474. _, rpath, err := static.VirtualPathToRealPath(vpath, u)
  475. if err != nil {
  476. g.RaiseError(err)
  477. return otto.FalseValue()
  478. }
  479. // Try to detect format using archiver library
  480. _, err = archiver.ByExtension(rpath)
  481. if err != nil {
  482. // Try by header
  483. f, err := os.Open(rpath)
  484. if err != nil {
  485. reply, _ := vm.ToValue(false)
  486. return reply
  487. }
  488. _, err = archiver.ByHeader(f)
  489. f.Close()
  490. if err != nil {
  491. reply, _ := vm.ToValue(false)
  492. return reply
  493. }
  494. }
  495. reply, _ := vm.ToValue(true)
  496. return reply
  497. })
  498. // listZipFileContents(vpath) => List contents of zip in json tree structure
  499. vm.Set("_ziplib_listZipFileContents", func(call otto.FunctionCall) otto.Value {
  500. vpath, err := call.Argument(0).ToString()
  501. if err != nil {
  502. g.RaiseError(err)
  503. return otto.NullValue()
  504. }
  505. vpath = static.RelativeVpathRewrite(scriptFsh, vpath, vm, u)
  506. if !u.CanRead(vpath) {
  507. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+vpath))
  508. }
  509. _, rpath, err := static.VirtualPathToRealPath(vpath, u)
  510. if err != nil {
  511. g.RaiseError(err)
  512. return otto.NullValue()
  513. }
  514. // Dispatch 7z archives to the dedicated helper
  515. if strings.EqualFold(filepath.Ext(rpath), ".7z") {
  516. jsonStr, err := sz7zListContentsJSON(rpath)
  517. if err != nil {
  518. g.RaiseError(err)
  519. return otto.NullValue()
  520. }
  521. reply, _ := vm.ToValue(jsonStr)
  522. return reply
  523. }
  524. r, err := zip.OpenReader(rpath)
  525. if err != nil {
  526. g.RaiseError(err)
  527. return otto.NullValue()
  528. }
  529. defer r.Close()
  530. // Build tree structure
  531. type Node struct {
  532. Name string `json:"name"`
  533. IsDir bool `json:"isDir"`
  534. Size int64 `json:"size"`
  535. Children map[string]*Node `json:"children,omitempty"`
  536. }
  537. root := &Node{
  538. Name: "/",
  539. IsDir: true,
  540. Children: make(map[string]*Node),
  541. }
  542. for _, f := range r.File {
  543. parts := strings.Split(filepath.ToSlash(f.Name), "/")
  544. current := root
  545. for i, part := range parts {
  546. if part == "" {
  547. continue
  548. }
  549. isLast := i == len(parts)-1
  550. if current.Children == nil {
  551. current.Children = make(map[string]*Node)
  552. }
  553. if _, exists := current.Children[part]; !exists {
  554. current.Children[part] = &Node{
  555. Name: part,
  556. IsDir: !isLast || f.FileInfo().IsDir(),
  557. }
  558. if isLast && !f.FileInfo().IsDir() {
  559. current.Children[part].Size = int64(f.UncompressedSize64)
  560. }
  561. }
  562. current = current.Children[part]
  563. }
  564. }
  565. jsonData, err := json.Marshal(root)
  566. if err != nil {
  567. g.RaiseError(err)
  568. return otto.NullValue()
  569. }
  570. reply, _ := vm.ToValue(string(jsonData))
  571. return reply
  572. })
  573. // listZipFileDir(zipVpath, dirPath) => List contents of specific directory in zip
  574. vm.Set("_ziplib_listZipFileDir", func(call otto.FunctionCall) otto.Value {
  575. zipVpath, err := call.Argument(0).ToString()
  576. if err != nil {
  577. g.RaiseError(err)
  578. return otto.NullValue()
  579. }
  580. dirPath, err := call.Argument(1).ToString()
  581. if err != nil {
  582. // Default to root if not provided
  583. dirPath = ""
  584. }
  585. zipVpath = static.RelativeVpathRewrite(scriptFsh, zipVpath, vm, u)
  586. if !u.CanRead(zipVpath) {
  587. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+zipVpath))
  588. }
  589. _, zipRpath, err := static.VirtualPathToRealPath(zipVpath, u)
  590. if err != nil {
  591. g.RaiseError(err)
  592. return otto.NullValue()
  593. }
  594. // Dispatch 7z archives to the dedicated helper
  595. if strings.EqualFold(filepath.Ext(zipRpath), ".7z") {
  596. filelist, err := szListDir(zipRpath, dirPath)
  597. if err != nil {
  598. g.RaiseError(err)
  599. return otto.NullValue()
  600. }
  601. reply, _ := vm.ToValue(filelist)
  602. return reply
  603. }
  604. // Open zip file
  605. r, err := zip.OpenReader(zipRpath)
  606. if err != nil {
  607. g.RaiseError(err)
  608. return otto.NullValue()
  609. }
  610. defer r.Close()
  611. // Normalize the directory path
  612. dirPath = filepath.ToSlash(strings.TrimPrefix(dirPath, "/"))
  613. if dirPath != "" && !strings.HasSuffix(dirPath, "/") {
  614. dirPath = dirPath + "/"
  615. }
  616. // Check if directory exists and collect immediate children
  617. var filelist []string
  618. dirExists := dirPath == "" // root always exists
  619. seenItems := make(map[string]bool)
  620. for _, f := range r.File {
  621. fName := filepath.ToSlash(f.Name)
  622. // Check if this file is in the target directory
  623. if dirPath == "" {
  624. // List root level items
  625. parts := strings.Split(fName, "/")
  626. if len(parts) > 0 {
  627. item := parts[0]
  628. if item != "" && !seenItems[item] {
  629. seenItems[item] = true
  630. // Check if it's a directory
  631. if len(parts) > 1 || f.FileInfo().IsDir() {
  632. filelist = append(filelist, item+"/")
  633. } else {
  634. filelist = append(filelist, item)
  635. }
  636. }
  637. }
  638. } else if strings.HasPrefix(fName, dirPath) {
  639. // Mark directory as existing
  640. dirExists = true
  641. // Get the relative path from the directory
  642. relPath := strings.TrimPrefix(fName, dirPath)
  643. if relPath != "" {
  644. parts := strings.Split(relPath, "/")
  645. if len(parts) > 0 {
  646. item := parts[0]
  647. if item != "" && !seenItems[item] {
  648. seenItems[item] = true
  649. // Check if it's a directory (has more parts or is a dir entry)
  650. if len(parts) > 1 || (len(parts) == 1 && strings.HasSuffix(relPath, "/")) {
  651. filelist = append(filelist, item+"/")
  652. } else {
  653. filelist = append(filelist, item)
  654. }
  655. }
  656. }
  657. }
  658. }
  659. }
  660. // If directory path was specified but doesn't exist, return error
  661. if dirPath != "" && !dirExists {
  662. g.RaiseError(errors.New("Directory not found in zip: " + dirPath))
  663. return otto.NullValue()
  664. }
  665. reply, _ := vm.ToValue(filelist)
  666. return reply
  667. })
  668. // getFileFromZip(zipVpath, filePathInZip) => Extract specific file from zip to tmp:/
  669. vm.Set("_ziplib_getFileFromZip", func(call otto.FunctionCall) otto.Value {
  670. zipVpath, err := call.Argument(0).ToString()
  671. if err != nil {
  672. g.RaiseError(err)
  673. return otto.NullValue()
  674. }
  675. fileInZip, err := call.Argument(1).ToString()
  676. if err != nil {
  677. g.RaiseError(err)
  678. return otto.NullValue()
  679. }
  680. zipVpath = static.RelativeVpathRewrite(scriptFsh, zipVpath, vm, u)
  681. if !u.CanRead(zipVpath) {
  682. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+zipVpath))
  683. }
  684. _, zipRpath, err := static.VirtualPathToRealPath(zipVpath, u)
  685. if err != nil {
  686. g.RaiseError(err)
  687. return otto.NullValue()
  688. }
  689. // Dispatch 7z archives
  690. if strings.EqualFold(filepath.Ext(zipRpath), ".7z") {
  691. r7z, err := sevenzip.OpenReader(zipRpath)
  692. if err != nil {
  693. g.RaiseError(err)
  694. return otto.NullValue()
  695. }
  696. defer r7z.Close()
  697. var target7z *sevenzip.File
  698. for _, f := range r7z.File {
  699. if filepath.ToSlash(f.Name) == filepath.ToSlash(fileInZip) {
  700. target7z = f
  701. break
  702. }
  703. }
  704. if target7z == nil {
  705. g.RaiseError(errors.New("File not found in archive: " + fileInZip))
  706. return otto.NullValue()
  707. }
  708. tmpFsh7z, err := u.GetFileSystemHandlerFromVirtualPath("tmp:/")
  709. if err != nil {
  710. g.RaiseError(err)
  711. return otto.NullValue()
  712. }
  713. tmpFilename7z := arozfs.Base(fileInZip)
  714. tmpVpath7z := "tmp:/" + tmpFilename7z
  715. tmpRpath7z, _ := tmpFsh7z.FileSystemAbstraction.VirtualPathToRealPath(tmpVpath7z, u.Username)
  716. rc7z, err := target7z.Open()
  717. if err != nil {
  718. g.RaiseError(err)
  719. return otto.NullValue()
  720. }
  721. defer rc7z.Close()
  722. if err = tmpFsh7z.FileSystemAbstraction.WriteStream(tmpRpath7z, rc7z, 0755); err != nil {
  723. g.RaiseError(err)
  724. return otto.NullValue()
  725. }
  726. u.SetOwnerOfFile(tmpFsh7z, tmpVpath7z)
  727. reply, _ := vm.ToValue(tmpVpath7z)
  728. return reply
  729. }
  730. // Open zip file
  731. r, err := zip.OpenReader(zipRpath)
  732. if err != nil {
  733. g.RaiseError(err)
  734. return otto.NullValue()
  735. }
  736. defer r.Close()
  737. // Find the file in zip
  738. var targetFile *zip.File
  739. for _, f := range r.File {
  740. if filepath.ToSlash(f.Name) == filepath.ToSlash(fileInZip) {
  741. targetFile = f
  742. break
  743. }
  744. }
  745. if targetFile == nil {
  746. g.RaiseError(errors.New("File not found in zip: " + fileInZip))
  747. return otto.NullValue()
  748. }
  749. // Extract to tmp
  750. tmpFsh, err := u.GetFileSystemHandlerFromVirtualPath("tmp:/")
  751. if err != nil {
  752. g.RaiseError(err)
  753. return otto.NullValue()
  754. }
  755. tmpFilename := arozfs.Base(fileInZip)
  756. tmpVpath := "tmp:/" + tmpFilename
  757. tmpRpath, _ := tmpFsh.FileSystemAbstraction.VirtualPathToRealPath(tmpVpath, u.Username)
  758. rc, err := targetFile.Open()
  759. if err != nil {
  760. g.RaiseError(err)
  761. return otto.NullValue()
  762. }
  763. defer rc.Close()
  764. err = tmpFsh.FileSystemAbstraction.WriteStream(tmpRpath, rc, 0755)
  765. if err != nil {
  766. g.RaiseError(err)
  767. return otto.NullValue()
  768. }
  769. u.SetOwnerOfFile(tmpFsh, tmpVpath)
  770. reply, _ := vm.ToValue(tmpVpath)
  771. return reply
  772. })
  773. // getCompressFileType(vpath) => Detect compression type
  774. vm.Set("_ziplib_getCompressFileType", func(call otto.FunctionCall) otto.Value {
  775. vpath, err := call.Argument(0).ToString()
  776. if err != nil {
  777. g.RaiseError(err)
  778. return otto.NullValue()
  779. }
  780. vpath = static.RelativeVpathRewrite(scriptFsh, vpath, vm, u)
  781. if !u.CanRead(vpath) {
  782. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+vpath))
  783. }
  784. _, rpath, err := static.VirtualPathToRealPath(vpath, u)
  785. if err != nil {
  786. g.RaiseError(err)
  787. return otto.NullValue()
  788. }
  789. // Check file extension and magic bytes
  790. ext := strings.ToLower(filepath.Ext(rpath))
  791. fileType := "unknown"
  792. switch ext {
  793. case ".zip":
  794. fileType = "zip"
  795. case ".7z":
  796. fileType = "7z"
  797. case ".tar":
  798. fileType = "tar"
  799. case ".gz":
  800. // Check if it's tar.gz
  801. if strings.HasSuffix(strings.ToLower(rpath), ".tar.gz") || strings.HasSuffix(strings.ToLower(rpath), ".tgz") {
  802. fileType = "tar.gz"
  803. } else {
  804. fileType = "gz"
  805. }
  806. case ".tgz":
  807. fileType = "tar.gz"
  808. default:
  809. // Try to detect by magic bytes
  810. f, err := os.Open(rpath)
  811. if err == nil {
  812. defer f.Close()
  813. magic := make([]byte, 4)
  814. f.Read(magic)
  815. // Check magic bytes
  816. if magic[0] == 0x50 && magic[1] == 0x4B && (magic[2] == 0x03 || magic[2] == 0x05) {
  817. fileType = "zip"
  818. } else if magic[0] == 0x37 && magic[1] == 0x7A && magic[2] == 0xBC && magic[3] == 0xAF {
  819. fileType = "7z"
  820. } else if magic[0] == 0x1F && magic[1] == 0x8B {
  821. fileType = "gz"
  822. }
  823. }
  824. }
  825. reply, _ := vm.ToValue(fileType)
  826. return reply
  827. })
  828. // extractAnyFile(sourceVpath, destVpath) => Extract based on file type detection
  829. vm.Set("_ziplib_extractAnyFile", func(call otto.FunctionCall) otto.Value {
  830. srcVpath, err := call.Argument(0).ToString()
  831. if err != nil {
  832. g.RaiseError(err)
  833. return otto.FalseValue()
  834. }
  835. destVpath, err := call.Argument(1).ToString()
  836. if err != nil {
  837. g.RaiseError(err)
  838. return otto.FalseValue()
  839. }
  840. srcVpath = static.RelativeVpathRewrite(scriptFsh, srcVpath, vm, u)
  841. destVpath = static.RelativeVpathRewrite(scriptFsh, destVpath, vm, u)
  842. if !u.CanRead(srcVpath) {
  843. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+srcVpath))
  844. }
  845. if !u.CanWrite(destVpath) {
  846. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+destVpath))
  847. }
  848. _, srcRpath, err := static.VirtualPathToRealPath(srcVpath, u)
  849. if err != nil {
  850. g.RaiseError(err)
  851. return otto.FalseValue()
  852. }
  853. destFsh, destRpath, err := static.VirtualPathToRealPath(destVpath, u)
  854. if err != nil {
  855. g.RaiseError(err)
  856. return otto.FalseValue()
  857. }
  858. // 7z is not handled by the archiver library; dispatch separately
  859. if strings.EqualFold(filepath.Ext(srcRpath), ".7z") {
  860. if err = sz7zExtractAll(srcRpath, destFsh, destRpath); err != nil {
  861. g.RaiseError(err)
  862. return otto.FalseValue()
  863. }
  864. destFsh.FileSystemAbstraction.Walk(destRpath, func(path string, info os.FileInfo, err error) error {
  865. if err == nil && !info.IsDir() {
  866. vp, _ := static.RealpathToVirtualpath(destFsh, path, u)
  867. u.SetOwnerOfFile(destFsh, vp)
  868. }
  869. return nil
  870. })
  871. reply, _ := vm.ToValue(true)
  872. return reply
  873. }
  874. // Detect format
  875. format, err := archiver.ByExtension(srcRpath)
  876. if err != nil {
  877. // Try by header
  878. f, err := os.Open(srcRpath)
  879. if err != nil {
  880. g.RaiseError(err)
  881. return otto.FalseValue()
  882. }
  883. format, err = archiver.ByHeader(f)
  884. f.Close()
  885. if err != nil {
  886. g.RaiseError(err)
  887. return otto.FalseValue()
  888. }
  889. }
  890. // Extract using appropriate archiver
  891. if u, ok := format.(archiver.Unarchiver); ok {
  892. err = u.Unarchive(srcRpath, destRpath)
  893. if err != nil {
  894. g.RaiseError(err)
  895. return otto.FalseValue()
  896. }
  897. } else {
  898. g.RaiseError(errors.New("format does not support extraction"))
  899. return otto.FalseValue()
  900. }
  901. // Update ownership
  902. destFsh.FileSystemAbstraction.Walk(destRpath, func(path string, info os.FileInfo, err error) error {
  903. if err == nil && !info.IsDir() {
  904. vp, _ := static.RealpathToVirtualpath(destFsh, path, u)
  905. u.SetOwnerOfFile(destFsh, vp)
  906. }
  907. return nil
  908. })
  909. reply, _ := vm.ToValue(true)
  910. return reply
  911. })
  912. // createAnyZipFile(sourceVpaths, outputVpath, format) => Create archive based on format
  913. vm.Set("_ziplib_createAnyZipFile", func(call otto.FunctionCall) otto.Value {
  914. sourcesObj, err := call.Argument(0).Export()
  915. if err != nil {
  916. g.RaiseError(err)
  917. return otto.FalseValue()
  918. }
  919. outputVpath, err := call.Argument(1).ToString()
  920. if err != nil {
  921. g.RaiseError(err)
  922. return otto.FalseValue()
  923. }
  924. format, err := call.Argument(2).ToString()
  925. if err != nil {
  926. // Default to zip
  927. format = "zip"
  928. }
  929. var sources []string
  930. switch v := sourcesObj.(type) {
  931. case []interface{}:
  932. for _, s := range v {
  933. if str, ok := s.(string); ok {
  934. sources = append(sources, static.RelativeVpathRewrite(scriptFsh, str, vm, u))
  935. }
  936. }
  937. case string:
  938. sources = append(sources, static.RelativeVpathRewrite(scriptFsh, v, vm, u))
  939. }
  940. outputVpath = static.RelativeVpathRewrite(scriptFsh, outputVpath, vm, u)
  941. for _, src := range sources {
  942. if !u.CanRead(src) {
  943. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+src))
  944. }
  945. }
  946. if !u.CanWrite(outputVpath) {
  947. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+outputVpath))
  948. }
  949. var sourceRpaths []string
  950. for _, src := range sources {
  951. _, rpath, err := static.VirtualPathToRealPath(src, u)
  952. if err != nil {
  953. g.RaiseError(err)
  954. return otto.FalseValue()
  955. }
  956. sourceRpaths = append(sourceRpaths, rpath)
  957. }
  958. outputFsh, outputRpath, err := static.VirtualPathToRealPath(outputVpath, u)
  959. if err != nil {
  960. g.RaiseError(err)
  961. return otto.FalseValue()
  962. }
  963. // Create archive based on format
  964. switch strings.ToLower(format) {
  965. case "zip":
  966. z := &archiver.Zip{}
  967. err = z.Archive(sourceRpaths, outputRpath)
  968. case "tar":
  969. tar := &archiver.Tar{}
  970. err = tar.Archive(sourceRpaths, outputRpath)
  971. case "tar.gz", "tgz", "targz":
  972. tgz := &archiver.TarGz{}
  973. err = tgz.Archive(sourceRpaths, outputRpath)
  974. case "gz", "gzip":
  975. g.RaiseError(errors.New("gz format requires createGzFile for single file compression"))
  976. return otto.FalseValue()
  977. default:
  978. g.RaiseError(errors.New("unsupported format: " + format))
  979. return otto.FalseValue()
  980. }
  981. if err != nil {
  982. g.RaiseError(err)
  983. return otto.FalseValue()
  984. }
  985. u.SetOwnerOfFile(outputFsh, outputVpath)
  986. reply, _ := vm.ToValue(true)
  987. return reply
  988. })
  989. // extractPartialZip(zipVpath, filePathsInZip, destVpath) => Extract specific files or folders from a zip
  990. vm.Set("_ziplib_extractPartialZip", func(call otto.FunctionCall) otto.Value {
  991. zipVpath, err := call.Argument(0).ToString()
  992. if err != nil {
  993. g.RaiseError(err)
  994. return otto.FalseValue()
  995. }
  996. pathsObj, err := call.Argument(1).Export()
  997. if err != nil {
  998. g.RaiseError(err)
  999. return otto.FalseValue()
  1000. }
  1001. destVpath, err := call.Argument(2).ToString()
  1002. if err != nil {
  1003. g.RaiseError(err)
  1004. return otto.FalseValue()
  1005. }
  1006. zipVpath = static.RelativeVpathRewrite(scriptFsh, zipVpath, vm, u)
  1007. destVpath = static.RelativeVpathRewrite(scriptFsh, destVpath, vm, u)
  1008. targetPaths := szParsePathsArg(pathsObj)
  1009. if targetPaths == nil {
  1010. g.RaiseError(errors.New("invalid paths format"))
  1011. return otto.FalseValue()
  1012. }
  1013. if !u.CanRead(zipVpath) {
  1014. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+zipVpath))
  1015. }
  1016. if !u.CanWrite(destVpath) {
  1017. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+destVpath))
  1018. }
  1019. _, zipRpath, err := static.VirtualPathToRealPath(zipVpath, u)
  1020. if err != nil {
  1021. g.RaiseError(err)
  1022. return otto.FalseValue()
  1023. }
  1024. destFsh, destRpath, err := static.VirtualPathToRealPath(destVpath, u)
  1025. if err != nil {
  1026. g.RaiseError(err)
  1027. return otto.FalseValue()
  1028. }
  1029. // Dispatch 7z archives
  1030. if strings.EqualFold(filepath.Ext(zipRpath), ".7z") {
  1031. r7z, err := sevenzip.OpenReader(zipRpath)
  1032. if err != nil {
  1033. g.RaiseError(err)
  1034. return otto.FalseValue()
  1035. }
  1036. defer r7z.Close()
  1037. for _, f := range r7z.File {
  1038. outRelPath := szMatchPartialPath(filepath.ToSlash(f.Name), targetPaths)
  1039. if outRelPath == "" {
  1040. continue
  1041. }
  1042. outPath := filepath.Join(destRpath, filepath.FromSlash(outRelPath))
  1043. if f.FileInfo().IsDir() {
  1044. destFsh.FileSystemAbstraction.MkdirAll(outPath, 0755)
  1045. continue
  1046. }
  1047. destFsh.FileSystemAbstraction.MkdirAll(filepath.Dir(outPath), 0755)
  1048. rc, err := f.Open()
  1049. if err != nil {
  1050. continue
  1051. }
  1052. destFsh.FileSystemAbstraction.WriteStream(outPath, rc, 0755)
  1053. rc.Close()
  1054. vp, err := static.RealpathToVirtualpath(destFsh, outPath, u)
  1055. if err == nil {
  1056. u.SetOwnerOfFile(destFsh, vp)
  1057. }
  1058. }
  1059. reply, _ := vm.ToValue(true)
  1060. return reply
  1061. }
  1062. r, err := zip.OpenReader(zipRpath)
  1063. if err != nil {
  1064. g.RaiseError(err)
  1065. return otto.FalseValue()
  1066. }
  1067. defer r.Close()
  1068. for _, f := range r.File {
  1069. outRelPath := szMatchPartialPath(filepath.ToSlash(f.Name), targetPaths)
  1070. if outRelPath == "" {
  1071. continue
  1072. }
  1073. outPath := filepath.Join(destRpath, filepath.FromSlash(outRelPath))
  1074. if f.FileInfo().IsDir() {
  1075. destFsh.FileSystemAbstraction.MkdirAll(outPath, 0755)
  1076. continue
  1077. }
  1078. destFsh.FileSystemAbstraction.MkdirAll(filepath.Dir(outPath), 0755)
  1079. rc, err := f.Open()
  1080. if err != nil {
  1081. continue
  1082. }
  1083. destFsh.FileSystemAbstraction.WriteStream(outPath, rc, 0755)
  1084. rc.Close()
  1085. vp, err := static.RealpathToVirtualpath(destFsh, outPath, u)
  1086. if err == nil {
  1087. u.SetOwnerOfFile(destFsh, vp)
  1088. }
  1089. }
  1090. reply, _ := vm.ToValue(true)
  1091. return reply
  1092. })
  1093. // addFileToZip(zipVpath, sourceVpath) => Add a file or folder to an existing zip by rebuilding it
  1094. vm.Set("_ziplib_addFileToZip", func(call otto.FunctionCall) otto.Value {
  1095. zipVpath, err := call.Argument(0).ToString()
  1096. if err != nil {
  1097. g.RaiseError(err)
  1098. return otto.FalseValue()
  1099. }
  1100. srcVpath, err := call.Argument(1).ToString()
  1101. if err != nil {
  1102. g.RaiseError(err)
  1103. return otto.FalseValue()
  1104. }
  1105. zipVpath = static.RelativeVpathRewrite(scriptFsh, zipVpath, vm, u)
  1106. srcVpath = static.RelativeVpathRewrite(scriptFsh, srcVpath, vm, u)
  1107. if !u.CanRead(zipVpath) {
  1108. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+zipVpath))
  1109. }
  1110. if !u.CanWrite(zipVpath) {
  1111. panic(vm.MakeCustomError("PermissionDenied", "Write access denied: "+zipVpath))
  1112. }
  1113. if !u.CanRead(srcVpath) {
  1114. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+srcVpath))
  1115. }
  1116. _, zipRpath, err := static.VirtualPathToRealPath(zipVpath, u)
  1117. if err != nil {
  1118. g.RaiseError(err)
  1119. return otto.FalseValue()
  1120. }
  1121. srcFsh, srcRpath, err := static.VirtualPathToRealPath(srcVpath, u)
  1122. if err != nil {
  1123. g.RaiseError(err)
  1124. return otto.FalseValue()
  1125. }
  1126. // Open existing zip for reading
  1127. r, err := zip.OpenReader(zipRpath)
  1128. if err != nil {
  1129. g.RaiseError(err)
  1130. return otto.FalseValue()
  1131. }
  1132. // Create temp file in the same directory to allow os.Rename
  1133. tmpFile, err := os.CreateTemp(filepath.Dir(zipRpath), "arozos_zip_*.tmp")
  1134. if err != nil {
  1135. r.Close()
  1136. g.RaiseError(err)
  1137. return otto.FalseValue()
  1138. }
  1139. tmpPath := tmpFile.Name()
  1140. w := zip.NewWriter(tmpFile)
  1141. // Copy all existing entries preserving compression
  1142. for _, f := range r.File {
  1143. if err := w.Copy(f); err != nil {
  1144. w.Close()
  1145. tmpFile.Close()
  1146. r.Close()
  1147. os.Remove(tmpPath)
  1148. g.RaiseError(err)
  1149. return otto.FalseValue()
  1150. }
  1151. }
  1152. r.Close()
  1153. // Collect new entries from source (file or directory)
  1154. type addEntry struct {
  1155. arcName string
  1156. rpath string
  1157. }
  1158. var entries []addEntry
  1159. srcBase := arozfs.Base(srcRpath)
  1160. if srcFsh.FileSystemAbstraction.IsDir(srcRpath) {
  1161. srcFsh.FileSystemAbstraction.Walk(srcRpath, func(path string, info os.FileInfo, werr error) error {
  1162. if werr != nil || info.IsDir() {
  1163. return nil
  1164. }
  1165. rel, err := filepath.Rel(filepath.Dir(srcRpath), path)
  1166. if err != nil {
  1167. rel = filepath.Join(srcBase, info.Name())
  1168. }
  1169. entries = append(entries, addEntry{arcName: filepath.ToSlash(rel), rpath: path})
  1170. return nil
  1171. })
  1172. } else {
  1173. entries = append(entries, addEntry{arcName: srcBase, rpath: srcRpath})
  1174. }
  1175. for _, entry := range entries {
  1176. fw, err := w.Create(entry.arcName)
  1177. if err != nil {
  1178. w.Close()
  1179. tmpFile.Close()
  1180. os.Remove(tmpPath)
  1181. g.RaiseError(err)
  1182. return otto.FalseValue()
  1183. }
  1184. rc, err := srcFsh.FileSystemAbstraction.ReadStream(entry.rpath)
  1185. if err != nil {
  1186. w.Close()
  1187. tmpFile.Close()
  1188. os.Remove(tmpPath)
  1189. g.RaiseError(err)
  1190. return otto.FalseValue()
  1191. }
  1192. _, copyErr := io.Copy(fw, rc)
  1193. rc.Close()
  1194. if copyErr != nil {
  1195. w.Close()
  1196. tmpFile.Close()
  1197. os.Remove(tmpPath)
  1198. g.RaiseError(copyErr)
  1199. return otto.FalseValue()
  1200. }
  1201. }
  1202. w.Close()
  1203. tmpFile.Close()
  1204. // Replace original zip with temp file
  1205. if renameErr := os.Rename(tmpPath, zipRpath); renameErr != nil {
  1206. // Fallback to copy+delete if rename fails (cross-device)
  1207. content, readErr := os.ReadFile(tmpPath)
  1208. os.Remove(tmpPath)
  1209. if readErr != nil {
  1210. g.RaiseError(renameErr)
  1211. return otto.FalseValue()
  1212. }
  1213. if writeErr := os.WriteFile(zipRpath, content, 0644); writeErr != nil {
  1214. g.RaiseError(writeErr)
  1215. return otto.FalseValue()
  1216. }
  1217. }
  1218. reply, _ := vm.ToValue(true)
  1219. return reply
  1220. })
  1221. // getZipFileInfo(zipVpath) => Return JSON with file count, dir count, compressed/uncompressed sizes
  1222. vm.Set("_ziplib_getZipFileInfo", func(call otto.FunctionCall) otto.Value {
  1223. zipVpath, err := call.Argument(0).ToString()
  1224. if err != nil {
  1225. g.RaiseError(err)
  1226. return otto.NullValue()
  1227. }
  1228. zipVpath = static.RelativeVpathRewrite(scriptFsh, zipVpath, vm, u)
  1229. if !u.CanRead(zipVpath) {
  1230. panic(vm.MakeCustomError("PermissionDenied", "Read access denied: "+zipVpath))
  1231. }
  1232. _, zipRpath, err := static.VirtualPathToRealPath(zipVpath, u)
  1233. if err != nil {
  1234. g.RaiseError(err)
  1235. return otto.NullValue()
  1236. }
  1237. if strings.EqualFold(filepath.Ext(zipRpath), ".7z") {
  1238. jsonStr, err := sz7zGetFileInfo(zipRpath)
  1239. if err != nil {
  1240. g.RaiseError(err)
  1241. return otto.NullValue()
  1242. }
  1243. reply, _ := vm.ToValue(jsonStr)
  1244. return reply
  1245. }
  1246. r, err := zip.OpenReader(zipRpath)
  1247. if err != nil {
  1248. g.RaiseError(err)
  1249. return otto.NullValue()
  1250. }
  1251. defer r.Close()
  1252. type ZipInfo struct {
  1253. FileCount int `json:"fileCount"`
  1254. DirCount int `json:"dirCount"`
  1255. TotalUncompressedSize int64 `json:"totalUncompressedSize"`
  1256. TotalCompressedSize int64 `json:"totalCompressedSize"`
  1257. }
  1258. info := ZipInfo{}
  1259. for _, f := range r.File {
  1260. if f.FileInfo().IsDir() {
  1261. info.DirCount++
  1262. } else {
  1263. info.FileCount++
  1264. info.TotalUncompressedSize += int64(f.UncompressedSize64)
  1265. info.TotalCompressedSize += int64(f.CompressedSize64)
  1266. }
  1267. }
  1268. jsonData, err := json.Marshal(info)
  1269. if err != nil {
  1270. g.RaiseError(err)
  1271. return otto.NullValue()
  1272. }
  1273. reply, _ := vm.ToValue(string(jsonData))
  1274. return reply
  1275. })
  1276. vm.Run(`
  1277. var ziplib = {};
  1278. ziplib.extractZipFile = _ziplib_extractZipFile;
  1279. ziplib.createZipFile = _ziplib_createZipFile;
  1280. ziplib.createTarFile = _ziplib_createTarFile;
  1281. ziplib.extractTarFile = _ziplib_extractTarFile;
  1282. ziplib.createTarGzFile = _ziplib_createTarGzFile;
  1283. ziplib.extractTarGzFile = _ziplib_extractTarGzFile;
  1284. ziplib.createGzFile = _ziplib_createGzFile;
  1285. ziplib.extractGzFile = _ziplib_extractGzFile;
  1286. // Generic functions
  1287. ziplib.isValidZipFile = _ziplib_isValidZipFile; // Check if file is valid archive file (zip, tar, tar.gz, gz)
  1288. ziplib.listZipFileContents = _ziplib_listZipFileContents; // List contents of zip in json tree structure
  1289. ziplib.listZipFileDir = _ziplib_listZipFileDir; // List contents of specific directory in zip
  1290. ziplib.getFileFromZip = _ziplib_getFileFromZip; // Get specific file from zip, stored temporary at tmp:/
  1291. ziplib.getCompressFileType = _ziplib_getCompressFileType;
  1292. ziplib.extractAnyFile = _ziplib_extractAnyFile; // Extract zip, tar, targz, gz, 7z based on file type
  1293. ziplib.createAnyZipFile = _ziplib_createAnyZipFile; // Create zip, tar, targz, gz based on input
  1294. // Zip-specific advanced functions
  1295. ziplib.extractPartialZip = _ziplib_extractPartialZip; // Extract selected files/folders from zip to dest
  1296. ziplib.addFileToZip = _ziplib_addFileToZip; // Add file or folder to existing zip
  1297. ziplib.getZipFileInfo = _ziplib_getZipFileInfo; // Get metadata (counts, sizes) about a zip
  1298. `)
  1299. // Extend ziplib with 7z support
  1300. g.inject7zLibFunctions(payload)
  1301. }
  1302. /*
  1303. Example Usages
  1304. // Extract a zip file to a destination folder
  1305. ziplib.extractZipFile("user:/documents/archive.zip", "user:/documents/extracted/");
  1306. // Create a zip file from multiple sources
  1307. ziplib.createZipFile(["user:/documents/file1.txt", "user:/documents/file2.txt"], "user:/backup.zip");
  1308. ziplib.createZipFile("user:/documents/single_file.txt", "user:/output.zip"); // Single file
  1309. // Create and extract tar archives
  1310. ziplib.createTarFile(["user:/data/folder1", "user:/data/folder2"], "user:/archive.tar");
  1311. ziplib.extractTarFile("user:/downloads/archive.tar", "user:/extracted/");
  1312. // Create and extract tar.gz archives
  1313. ziplib.createTarGzFile(["user:/logs/"], "user:/backup/logs.tar.gz");
  1314. ziplib.extractTarGzFile("user:/downloads/backup.tar.gz", "user:/restore/");
  1315. // Compress and decompress single files with gzip
  1316. ziplib.createGzFile("user:/documents/large_file.txt", "user:/compressed/large_file.txt.gz");
  1317. ziplib.extractGzFile("user:/compressed/data.gz", "user:/decompressed/data");
  1318. // Check if a file is a valid archive (supports zip, tar, tar.gz, gz)
  1319. if (ziplib.isValidZipFile("user:/downloads/file.zip")) {
  1320. console.log("Valid archive file");
  1321. }
  1322. if (ziplib.isValidZipFile("user:/downloads/backup.tar.gz")) {
  1323. console.log("Valid tar.gz archive");
  1324. }
  1325. // List zip contents as JSON tree structure
  1326. var contents = JSON.parse(ziplib.listZipFileContents("user:/archive.zip"));
  1327. console.log(contents); // Returns nested tree structure with files and folders
  1328. // List root contents of zip
  1329. var rootFiles = ziplib.listZipFileDir("user:/archive.zip", "");
  1330. console.log(rootFiles); // Returns ["folder1/", "folder2/", "file.txt"]
  1331. // List contents of specific folder in zip
  1332. var folderContents = ziplib.listZipFileDir("user:/archive.zip", "folder1");
  1333. console.log(folderContents); // Returns ["subfolder/", "file1.txt", "file2.txt"]
  1334. // List contents with path
  1335. var deepContents = ziplib.listZipFileDir("user:/archive.zip", "folder1/subfolder");
  1336. console.log(deepContents); // Returns only immediate children of folder1/subfolder/ // Extract a specific file from zip to tmp:/
  1337. var tmpPath = ziplib.getFileFromZip("user:/archive.zip", "folder1/important.txt");
  1338. console.log("File extracted to: " + tmpPath); // Returns "tmp:/important.txt"
  1339. // Detect compression type
  1340. var type = ziplib.getCompressFileType("user:/unknown_file.archive");
  1341. console.log("Archive type: " + type); // Returns "zip", "tar", "tar.gz", "gz", or "unknown"
  1342. // Extract any supported archive format (auto-detect)
  1343. ziplib.extractAnyFile("user:/downloads/archive.unknown", "user:/extracted/");
  1344. // Create archive with specific format
  1345. ziplib.createAnyZipFile(["user:/data/"], "user:/backup.zip", "zip");
  1346. ziplib.createAnyZipFile(["user:/data/"], "user:/backup.tar", "tar");
  1347. ziplib.createAnyZipFile(["user:/data/"], "user:/backup.tar.gz", "tar.gz");
  1348. // Error handling examples
  1349. try {
  1350. ziplib.extractZipFile("user:/nonexistent.zip", "user:/output/");
  1351. } catch (e) {
  1352. console.log("Error: " + e.message);
  1353. }
  1354. // Working with relative paths (when script is in user:/scripts/)
  1355. ziplib.createZipFile(["../documents/data.txt"], "../backup.zip"); // Relative to script location
  1356. // Batch operations
  1357. var filesToZip = [
  1358. "user:/documents/report.pdf",
  1359. "user:/documents/data.csv",
  1360. "user:/documents/images/"
  1361. ];
  1362. ziplib.createZipFile(filesToZip, "user:/archive/batch_backup.zip");
  1363. */