public static function patchFile($item, $info, $algorithm, $patch) { switch ($algorithm) { case 'bsdiff': case 'xdelta': case 'vcdiff': break; case 'xdiff': if (!function_exists('xdiff_file_patch_binary')) { throw new Exception("=xdiff not available"); } break; default: throw new Exception("Invalid algorithm '{$algorithm}'", Z_ERROR_INVALID_INPUT); } $originalInfo = Zotero_Storage::getLocalFileItemInfo($item); $basePath = "/tmp/zfsupload/"; $path = $basePath . $info->hash . "_" . uniqid() . "/"; mkdir($path, 0777, true); $cleanup = function () use($basePath, $path) { unlink("original"); unlink("patch"); unlink("new"); chdir($basePath); rmdir($path); }; $e = null; try { // Download file from S3 to temp directory if (!Zotero_Storage::downloadFile($originalInfo, $path, "original")) { throw new Exception("Error downloading original file"); } chdir($path); // Save body to temp file file_put_contents("patch", $patch); // Patch file switch ($algorithm) { case 'bsdiff': exec('bspatch original new patch 2>&1', $output, $ret); if ($ret) { throw new Exception("Error applying patch ({$ret}): " . implode("\n", $output)); } if (!file_exists("new")) { throw new Exception("Error applying patch ({$ret})"); } break; case 'xdelta': case 'vcdiff': exec('xdelta3 -d -s original patch new 2>&1', $output, $ret); if ($ret) { if ($ret == 2) { throw new Exception("Invalid delta", Z_ERROR_INVALID_INPUT); } throw new Exception("Error applying patch ({$ret}): " . implode("\n", $output)); } if (!file_exists("new")) { throw new Exception("Error applying patch ({$ret})"); } break; case 'xdiff': $ret = xdiff_file_patch_binary("original", "patch", "new"); if (!$ret) { throw new Exception("Error applying patch"); } break; } // Check MD5 hash if (md5_file("new") != $info->hash) { $cleanup(); throw new HTTPException("Patched file does not match hash", 409); } // Check file size if (filesize("new") != $info->size) { $cleanup(); throw new HTTPException("Patched file size does not match " . "(" . filesize("new") . " != {$info->size})", 409); } // If ZIP, make sure it's a ZIP if ($info->zip && file_get_contents("new", false, null, 0, 4) != "PK" . chr(03) . chr(04)) { $cleanup(); throw new HTTPException("Patched file is not a ZIP file", 409); } // Upload to S3 $t = $info->contentType . ($info->contentType && $info->charset ? "; charset={$info->charset}" : ""); $storageFileID = Zotero_Storage::uploadFile($info, "new", $t); } catch (Exception $e) { //$cleanup(); throw $e; } return $storageFileID; }