--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2019 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+
+Prado::using('Application.API.Class.APIModule');
+
+/**
+ * Tools used to show list files command output.
+ */
+class BList extends APIModule {
+
+ /**
+ * Pattern to get only files from list files output.
+ */
+ const LIST_FILES_OUTPUT_PATTERN = '/^\| (?!(filename|jobid|\d))(?P<path>.+) \|$/i';
+
+ /**
+ * Parse list files output.
+ *
+ * @param array $output raw list files output lines
+ * @return array parsed list files paths
+ */
+ public function parseListFilesOutput(array $output) {
+ $result = array();
+ for ($i = 0; $i < count($output); $i++) {
+ if (preg_match(self::LIST_FILES_OUTPUT_PATTERN, $output[$i], $match) === 1) {
+ $result[] = trim($match['path']);
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Find file list items by given keyword.
+ *
+ * @param array $file_list file list
+ * @param string $keyword keyword to find
+ * @return array search result (items)
+ */
+ public function findFileListItems($file_list, $keyword) {
+ $result = array();
+ for ($i = 0; $i < count($file_list); $i++) {
+ if (preg_match('!' . preg_quote($keyword, '!') . '!i', $file_list[$i], $match) === 1) {
+ $result[] = $file_list[$i];
+ }
+ }
+ return $result;
+ }
+
+}
+?>
--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2019 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+/**
+ * List files from 'list files jobid=xx' bconsole command.
+ */
+class JobFiles extends BaculumAPIServer {
+
+ public function get() {
+ $misc = $this->getModule('misc');
+ $jobid = $this->Request->contains('id') ? intval($this->Request['id']) : 0;
+ $type = $this->Request->contains('type') && $misc->isValidListFilesType($this->Request['type']) ? $this->Request['type'] : null;
+ $offset = $this->Request->contains('offset') ? intval($this->Request['offset']) : 0;
+ $limit = $this->Request->contains('limit') ? intval($this->Request['limit']) : 0;
+ $search = $this->Request->contains('search') && $misc->isValidPath($this->Request['search']) ? $this->Request['search'] : null;
+
+ $result = $this->getModule('bconsole')->bconsoleCommand(
+ $this->director,
+ array('.jobs')
+ );
+ if ($result->exitcode === 0) {
+ array_shift($result->output);
+ $job = $this->getModule('job')->getJobById($jobid);
+ if (is_object($job) && in_array($job->name, $result->output)) {
+ $cmd = array('list', 'files');
+ if (is_string($type)) {
+ /**
+ * NOTE: type param has to be used BEFORE jobid=xx, otherwise it doesn't work.
+ * This behavior is also described in Bacula source code (src/dird/ua_output.c).
+ */
+ $cmd[] = 'type="' . $type . '"';
+ }
+ $cmd[] = 'jobid="' . $jobid . '"';
+ $result = $this->getModule('bconsole')->bconsoleCommand(
+ $this->director,
+ $cmd
+ );
+ if ($result->exitcode === 0) {
+ $file_list = $this->getModule('list')->parseListFilesOutput($result->output);
+ if (is_string($search)) {
+ // Find items
+ $file_list = $this->getModule('list')->findFileListItems($file_list, $search);
+ }
+ $total_items = count($file_list);
+ if ($offset > 0) {
+ if ($limit > 0) {
+ $file_list = array_slice($file_list, $offset, $limit);
+ } else {
+ $file_list = array_slice($file_list, $offset);
+ }
+ } elseif ($limit > 0) {
+ $file_list = array_slice($file_list, 0, $limit);
+ }
+ $this->output = array('items' => $file_list, 'total' => $total_items);
+ $this->error = GenericError::ERROR_NO_ERRORS;
+ } else {
+ $this->output = $result->output;
+ $this->error = $result->exitcode;
+ }
+ } else {
+ $this->output = JobError::MSG_ERROR_JOB_DOES_NOT_EXISTS;
+ $this->error = JobError::ERROR_JOB_DOES_NOT_EXISTS;
+ }
+ } else {
+ $this->output = $result->output;
+ $this->error = $result->exitcode;
+ }
+ }
+}
+?>
<module id="status_fd" class="Application.API.Class.StatusClient" />
<!-- component actions modules -->
<module id="comp_actions" class="Application.API.Class.ComponentActions" />
- <!-- misc modules -->
+ <!-- bconsole command modules -->
<module id="ls" class="Application.API.Class.Ls" />
+ <module id="list" class="Application.API.Class.BList" />
</modules>
</configuration>
<url ServiceParameter="JobRun" pattern="api/v1/jobs/run/" />
<url ServiceParameter="JobCancel" pattern="api/v1/jobs/{id}/cancel/" parameters.id="\d+"/>
<url ServiceParameter="JobTotals" pattern="api/v1/jobs/totals/" />
+ <url ServiceParameter="JobFiles" pattern="api/v1/jobs/{id}/files/" parameters.id="\d+" />
<url ServiceParameter="RestoreRun" pattern="api/v1/jobs/restore/" />
<!-- bvfs endpoints-->
<url ServiceParameter="BVFSUpdate" pattern="api/v1/bvfs/update/" />
]
}
},
+ "/api/v1/jobs/{jobid}/files": {
+ "get": {
+ "tags": ["jobs"],
+ "summary": "Show job files and directories",
+ "description": "Get job file and directory list.",
+ "responses": {
+ "200": {
+ "description": "Show job files output list",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "output": {
+ "type": "object",
+ "properties": {
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "description": "Show job files output list"
+ }
+ },
+ "total": {
+ "type": "integer",
+ "description": "Total number of items"
+ }
+ }
+ },
+ "error": {
+ "type": "integer",
+ "description": "Error code",
+ "enum": [0, 1, 2, 3, 4, 5, 6, 7, 11, 50, 1000]
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "jobid",
+ "in": "path",
+ "description": "Job identifier",
+ "required": true,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "type",
+ "in": "query",
+ "description": "List item type",
+ "required": false,
+ "schema": {
+ "type": "string",
+ "enum": ["deleted", "all"]
+ }
+ },
+ {
+ "name": "offset",
+ "in": "query",
+ "description": "Result items offset",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "limit",
+ "in": "query",
+ "description": "Result items limit",
+ "required": false,
+ "schema": {
+ "type": "integer"
+ }
+ },
+ {
+ "name": "search",
+ "in": "query",
+ "description": "Search keyword in item list",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ]
+ }
+ },
"/api/v1/jobs/resnames": {
"get": {
"tags": ["jobs"],
}
public function isValidPath($path) {
- return (preg_match('/^[\p{L}\p{N}\p{Z}\p{Sc}\[\]\-\'\/\\(){}:.#~_,+!$]{0,10000}$/u', $path) === 1);
+ return (preg_match('/^[\p{L}\p{N}\p{Z}\p{Sc}\p{Pd}\[\]\-\'\/\\(){}:.#~_,+!$]{0,10000}$/u', $path) === 1);
}
public function isValidReplace($replace) {
return (preg_match('/^[a-zA-Z0-9]+$/', $str) === 1);
}
+ public function isValidListFilesType($type) {
+ return (preg_match('/^(all|deleted)$/', $type) === 1);
+ }
+
public function escapeCharsToConsole($path) {
return preg_replace('/([$])/', '\\\${1}', $path);
}