"unicode-width",
]
+[[package]]
+name = "content_inspector"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "core-foundation"
version = "0.9.3"
"chrono",
"clap",
"clap_complete",
+ "content_inspector",
"diqwest",
"form_urlencoded",
"futures",
walkdir = "2.3"
form_urlencoded = "1.0"
alphanumeric-sort = "1.4"
+content_inspector = "0.2.4"
[features]
default = ["tls"]
}
.main {
- padding: 3em 1em 0;
+ padding: 3.3em 1em 0;
}
.empty-folder {
- padding-top: 1rem;
font-style: italic;
}
padding-right: 1em;
}
+.editor {
+ width: 100%;
+ height: calc(100vh - 5rem);
+ border: 1px solid #ced4da;
+ outline: none;
+}
+
+.save-btn {
+ margin-left: auto;
+ margin-right: 2em;
+ cursor: pointer;
+ user-select: none;
+}
+
+.not-editable {
+ font-style: italic;
+}
+
+
@media (min-width: 768px) {
.path a {
min-width: 400px;
<link rel="icon" type="image/x-icon" href="__ASSERTS_PREFIX__favicon.ico">
<link rel="stylesheet" href="__ASSERTS_PREFIX__index.css">
<script>
- DATA = __INDEX_DATA__
+ DATA = __INDEX_DATA__
</script>
<script src="__ASSERTS_PREFIX__index.js"></script>
</head>
+
<body>
<div class="head">
<div class="breadcrumb"></div>
<div class="toolbox">
<div>
<a href="?zip" class="zip-root hidden" title="Download folder as a .zip file">
- <svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
+ <svg width="16" height="16" viewBox="0 0 16 16">
+ <path
+ d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
+ <path
+ d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z" />
+ </svg>
+ </a>
+ <a href="" class="download hidden" title="Download file" download="">
+ <svg width="16" height="16" viewBox="0 0 16 16">
+ <path
+ d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
+ <path
+ d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z" />
+ </svg>
</a>
</div>
<div class="control upload-file hidden" title="Upload files">
<label for="file">
- <svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z"/></svg>
+ <svg width="16" height="16" viewBox="0 0 16 16">
+ <path
+ d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
+ <path
+ d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
+ </svg>
</label>
<input type="file" id="file" name="file" multiple>
</div>
<div class="control new-folder hidden" title="New folder">
<svg width="16" height="16" viewBox="0 0 16 16">
- <path d="m.5 3 .04.87a1.99 1.99 0 0 0-.342 1.311l.637 7A2 2 0 0 0 2.826 14H9v-1H2.826a1 1 0 0 1-.995-.91l-.637-7A1 1 0 0 1 2.19 4h11.62a1 1 0 0 1 .996 1.09L14.54 8h1.005l.256-2.819A2 2 0 0 0 13.81 3H9.828a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 6.172 1H2.5a2 2 0 0 0-2 2zm5.672-1a1 1 0 0 1 .707.293L7.586 3H2.19c-.24 0-.47.042-.683.12L1.5 2.98a1 1 0 0 1 1-.98h3.672z"/>
- <path d="M13.5 10a.5.5 0 0 1 .5.5V12h1.5a.5.5 0 1 1 0 1H14v1.5a.5.5 0 1 1-1 0V13h-1.5a.5.5 0 0 1 0-1H13v-1.5a.5.5 0 0 1 .5-.5z"/>
+ <path
+ d="m.5 3 .04.87a1.99 1.99 0 0 0-.342 1.311l.637 7A2 2 0 0 0 2.826 14H9v-1H2.826a1 1 0 0 1-.995-.91l-.637-7A1 1 0 0 1 2.19 4h11.62a1 1 0 0 1 .996 1.09L14.54 8h1.005l.256-2.819A2 2 0 0 0 13.81 3H9.828a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 6.172 1H2.5a2 2 0 0 0-2 2zm5.672-1a1 1 0 0 1 .707.293L7.586 3H2.19c-.24 0-.47.042-.683.12L1.5 2.98a1 1 0 0 1 1-.98h3.672z" />
+ <path
+ d="M13.5 10a.5.5 0 0 1 .5.5V12h1.5a.5.5 0 1 1 0 1H14v1.5a.5.5 0 1 1-1 0V13h-1.5a.5.5 0 0 1 0-1H13v-1.5a.5.5 0 0 1 .5-.5z" />
</svg>
</div>
</div>
<form class="searchbar hidden">
<div class="icon">
- <svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/></svg>
+ <svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
+ <path
+ d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z" />
+ </svg>
</div>
<input id="search" name="q" type="text" maxlength="128" autocomplete="off" tabindex="1">
<input type="submit" hidden />
</form>
+ <div class="save-btn hidden" title="Save file">
+ <svg viewBox="0 0 1024 1024" width="24" height="24">
+ <path
+ d="M426.666667 682.666667v42.666666h170.666666v-42.666666h-170.666666z m-42.666667-85.333334h298.666667v128h42.666666V418.133333L605.866667 298.666667H298.666667v426.666666h42.666666v-128h42.666667z m260.266667-384L810.666667 379.733333V810.666667H213.333333V213.333333h430.933334zM341.333333 341.333333h85.333334v170.666667H341.333333V341.333333z"
+ fill="#444444" p-id="8311"></path>
+ </svg>
+ </div>
</div>
<div class="main">
- <div class="empty-folder hidden"></div>
- <table class="uploaders-table hidden">
- <thead>
- <tr>
- <th class="cell-name" colspan="2">Name</th>
- <th class="cell-status">Progress</th>
- </tr>
- </thead>
- </table>
- <table class="paths-table hidden">
- <thead>
- </thead>
- <tbody>
- </tbody>
- </table>
+ <div class="index-page hidden">
+ <div class="empty-folder hidden"></div>
+ <table class="uploaders-table hidden">
+ <thead>
+ <tr>
+ <th class="cell-name" colspan="2">Name</th>
+ <th class="cell-status">Progress</th>
+ </tr>
+ </thead>
+ </table>
+ <table class="paths-table hidden">
+ <thead>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ <div class="editor-page hidden">
+ <div class="not-editable hidden"></div>
+ <textarea class="editor hidden" cols="10"></textarea>
+ </div>
</div>
<script>
window.addEventListener("DOMContentLoaded", ready);
</script>
</body>
+
</html>
\ No newline at end of file
*/
/**
- * @typedef {object} DATA
+ * @typedef {IndexDATA|EditDATA} DATA
+ */
+
+/**
+ * @typedef {object} IndexDATA
* @property {string} href
* @property {string} uri_prefix
+ * @property {"Index"} kind
* @property {PathItem[]} paths
* @property {boolean} allow_upload
* @property {boolean} allow_delete
* @property {boolean} dir_exists
*/
+/**
+ * @typedef {object} EditDATA
+ * @property {string} href
+ * @property {string} uri_prefix
+ * @property {"Edit"} kind
+ * @property {string} editable
+ */
+
/**
* @type {DATA} DATA
*/
/**
* @type Element
*/
-let $newFolder;
-/**
- * @type Element
- */
-let $searchbar;
+let $editor;
+
+function ready() {
+ document.title = `Index of ${DATA.href} - Dufs`;
+ $pathsTable = document.querySelector(".paths-table")
+ $pathsTableHead = document.querySelector(".paths-table thead");
+ $pathsTableBody = document.querySelector(".paths-table tbody");
+ $uploadersTable = document.querySelector(".uploaders-table");
+ $emptyFolder = document.querySelector(".empty-folder");
+ $editor = document.querySelector(".editor");
+
+ addBreadcrumb(DATA.href, DATA.uri_prefix);
+
+ if (DATA.kind == "Index") {
+
+ document.querySelector(".index-page").classList.remove("hidden");
+
+ if (DATA.allow_search) {
+ setupSearch()
+ }
+
+ if (DATA.allow_archive) {
+ document.querySelector(".zip-root").classList.remove("hidden");
+ }
+
+ renderPathsTableHead();
+ renderPathsTableBody();
+
+ if (DATA.allow_upload) {
+ dropzone();
+ setupUpload();
+ }
+ } else if (DATA.kind == "Edit") {
+ setupEditor();
+ }
+}
+
class Uploader {
/**
upload() {
const { idx, name } = this;
- const url = getUrl(name);
+ const url = newUrl(name);
const encodedName = encodedStr(name);
$uploadersTable.insertAdjacentHTML("beforeend", `
<tr id="upload${idx}" class="uploader">
<td class="path cell-icon">
- ${getSvg()}
+ ${getPathSvg()}
</td>
<td class="path cell-name">
<a href="${url}">${encodedName}</a>
ajax() {
Uploader.runnings += 1;
- const url = getUrl(this.name);
+ const url = newUrl(this.name);
this.lastUptime = Date.now();
const ajax = new XMLHttpRequest();
ajax.upload.addEventListener("progress", e => this.progress(e), false);
*/
function addPath(file, index) {
const encodedName = encodedStr(file.name);
- let url = getUrl(file.name)
+ let url = newUrl(file.name)
let actionDelete = "";
let actionDownload = "";
let actionMove = "";
$pathsTableBody.insertAdjacentHTML("beforeend", `
<tr id="addPath${index}">
<td class="path cell-icon">
- ${getSvg(file.path_type)}
+ ${getPathSvg(file.path_type)}
</td>
<td class="path cell-name">
- <a href="${url}">${encodedName}</a>
+ <a href="${url}?edit" target="_blank">${encodedName}</a>
</td>
<td class="cell-mtime">${formatMtime(file.mtime)}</td>
<td class="cell-size">${formatSize(file.size).join(" ")}</td>
if (!confirm(`Delete \`${file.name}\`?`)) return;
try {
- const res = await fetch(getUrl(file.name), {
+ const res = await fetch(newUrl(file.name), {
method: "DELETE",
});
- if (res.status >= 200 && res.status < 300) {
- document.getElementById(`addPath${index}`).remove();
- DATA.paths[index] = null;
- if (!DATA.paths.find(v => !!v)) {
- $pathsTable.classList.add("hidden");
- $emptyFolder.textContent = dirEmptyNote;
- $emptyFolder.classList.remove("hidden");
- }
- } else {
- throw new Error(await res.text())
+ await assertFetch(res);
+ document.getElementById(`addPath${index}`).remove();
+ DATA.paths[index] = null;
+ if (!DATA.paths.find(v => !!v)) {
+ $pathsTable.classList.add("hidden");
+ $emptyFolder.textContent = dirEmptyNote;
+ $emptyFolder.classList.remove("hidden");
}
} catch (err) {
alert(`Cannot delete \`${file.name}\`, ${err.message}`);
const file = DATA.paths[index];
if (!file) return;
- const fileUrl = getUrl(file.name);
+ const fileUrl = newUrl(file.name);
const fileUrlObj = new URL(fileUrl)
const prefix = DATA.uri_prefix.slice(0, -1);
"Destination": newFileUrl,
}
});
- if (res.status >= 200 && res.status < 300) {
- location.href = newFileUrl.split("/").slice(0, -1).join("/")
- } else {
- throw new Error(await res.text())
- }
+ await assertFetch(res);
+ location.href = newFileUrl.split("/").slice(0, -1).join("/")
} catch (err) {
alert(`Cannot move \`${filePath}\` to \`${newPath}\`, ${err.message}`);
}
* Setup searchbar
*/
function setupSearch() {
+ const $searchbar = document.querySelector(".searchbar");
$searchbar.classList.remove("hidden");
$searchbar.addEventListener("submit", event => {
event.preventDefault();
const formData = new FormData($searchbar);
const q = formData.get("q");
- let href = getUrl();
+ let href = baseUrl();
if (q) {
href += "?q=" + q;
}
}
}
-/**
- * Setup upload
- */
function setupUpload() {
+ const $newFolder = document.querySelector(".new-folder");
$newFolder.classList.remove("hidden");
$newFolder.addEventListener("click", () => {
const name = prompt("Enter folder name");
});
}
+async function setupEditor() {
+ document.querySelector(".editor-page").classList.remove("hidden");;
+
+ const $download = document.querySelector(".download")
+ $download.classList.remove("hidden");
+ $download.href = baseUrl()
+
+ if (!DATA.editable) {
+ const $notEditable = document.querySelector(".not-editable");
+ $notEditable.classList.remove("hidden");
+ $notEditable.textContent = "File is binary or too large.";
+ return;
+ }
+
+ const $saveBtn = document.querySelector(".save-btn");
+ $saveBtn.classList.remove("hidden");
+ $saveBtn.addEventListener("click", saveChange);
+
+ $editor.classList.remove("hidden");
+ try {
+ const res = await fetch(baseUrl());
+ await assertFetch(res);
+ const text = await res.text();
+ $editor.value = text;
+ } catch (err) {
+ alert(`Failed get file, ${err.message}`);
+ }
+}
+
+/**
+ * Save editor change
+ */
+async function saveChange() {
+ try {
+ await fetch(baseUrl(), {
+ method: "PUT",
+ body: $editor.value,
+ });
+ } catch (err) {
+ alert(`Failed to save file, ${err.message}`);
+ }
+}
+
/**
* Create a folder
* @param {string} name
*/
async function createFolder(name) {
- const url = getUrl(name);
+ const url = newUrl(name);
try {
const res = await fetch(url, {
method: "MKCOL",
});
- if (res.status >= 200 && res.status < 300) {
- location.href = url;
- }
+ await assertFetch(res);
+ location.href = url;
} catch (err) {
alert(`Cannot create folder \`${name}\`, ${err.message}`);
}
}
-function getUrl(name) {
- let url = location.href.split('?')[0];
+function newUrl(name) {
+ let url = baseUrl();
if (!url.endsWith("/")) url += "/";
- if (!name) return url;
url += name.split("/").map(encodeURIComponent).join("/");
return url;
}
-function getSvg(path_type) {
+function baseUrl() {
+ return location.href.split('?')[0];
+}
+
+function getPathSvg(path_type) {
switch (path_type) {
case "Dir":
return `<svg height="16" viewBox="0 0 14 16" width="14"><path fill-rule="evenodd" d="M13 4H7V3c0-.66-.31-1-1-1H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zM6 4H1V3h5v1z"></path></svg>`;
});
}
-function ready() {
- document.title = `Index of ${DATA.href} - Dufs`;
- $pathsTable = document.querySelector(".paths-table")
- $pathsTableHead = document.querySelector(".paths-table thead");
- $pathsTableBody = document.querySelector(".paths-table tbody");
- $uploadersTable = document.querySelector(".uploaders-table");
- $emptyFolder = document.querySelector(".empty-folder");
- $newFolder = document.querySelector(".new-folder");
- $searchbar = document.querySelector(".searchbar");
-
- if (DATA.allow_search) {
- setupSearch()
- }
-
- if (DATA.allow_archive) {
- document.querySelector(".zip-root").classList.remove("hidden");
- }
-
- addBreadcrumb(DATA.href, DATA.uri_prefix);
- renderPathsTableHead();
- renderPathsTableBody();
-
- if (DATA.allow_upload) {
- dropzone();
- setupUpload();
+async function assertFetch(res) {
+ if (!(res.status >= 200 && res.status < 300)) {
+ throw new Error(await res.text())
}
}
use std::sync::Arc;
use std::time::SystemTime;
use tokio::fs::File;
-use tokio::io::{AsyncSeekExt, AsyncWrite};
+use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWrite};
use tokio::{fs, io};
use tokio_util::io::StreamReader;
use uuid::Uuid;
const FAVICON_ICO: &[u8] = include_bytes!("../assets/favicon.ico");
const INDEX_NAME: &str = "index.html";
const BUF_SIZE: usize = 65536;
+const TEXT_MAX_SIZE: u64 = 4194304; // 4M
pub struct Server {
args: Arc<Args>,
.await?;
}
} else if is_file {
- self.handle_send_file(path, headers, head_only, &mut res)
- .await?;
+ if query_params.contains_key("edit") {
+ self.handle_edit_file(path, head_only, &mut res).await?;
+ } else {
+ self.handle_send_file(path, headers, head_only, &mut res)
+ .await?;
+ }
} else if render_spa {
self.handle_render_spa(path, headers, head_only, &mut res)
.await?;
Ok(())
}
+ async fn handle_edit_file(
+ &self,
+ path: &Path,
+ head_only: bool,
+ res: &mut Response,
+ ) -> BoxResult<()> {
+ let (file, meta) = tokio::join!(fs::File::open(path), fs::metadata(path),);
+ let (file, meta) = (file?, meta?);
+ let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?));
+ let mut buffer: Vec<u8> = vec![];
+ file.take(1024).read_to_end(&mut buffer).await?;
+ let editable = meta.len() <= TEXT_MAX_SIZE && content_inspector::inspect(&buffer).is_text();
+ let data = EditData {
+ href,
+ kind: DataKind::Edit,
+ uri_prefix: self.args.uri_prefix.clone(),
+ allow_upload: self.args.allow_upload,
+ allow_delete: self.args.allow_delete,
+ editable,
+ };
+ res.headers_mut()
+ .typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
+ let output = self
+ .html
+ .replace("__ASSERTS_PREFIX__", &self.assets_prefix)
+ .replace("__INDEX_DATA__", &serde_json::to_string(&data).unwrap());
+ res.headers_mut()
+ .typed_insert(ContentLength(output.as_bytes().len() as u64));
+ if head_only {
+ return Ok(());
+ }
+ *res.body_mut() = output.into();
+ Ok(())
+ }
+
async fn handle_propfind_dir(
&self,
path: &Path,
}
let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?));
let data = IndexData {
+ kind: DataKind::Index,
href,
uri_prefix: self.args.uri_prefix.clone(),
allow_upload: self.args.allow_upload,
}
}
+#[derive(Debug, Serialize)]
+enum DataKind {
+ Index,
+ Edit,
+}
+
#[derive(Debug, Serialize)]
struct IndexData {
href: String,
+ kind: DataKind,
uri_prefix: String,
allow_upload: bool,
allow_delete: bool,
paths: Vec<PathItem>,
}
+#[derive(Debug, Serialize)]
+struct EditData {
+ href: String,
+ kind: DataKind,
+ uri_prefix: String,
+ allow_upload: bool,
+ allow_delete: bool,
+ editable: bool,
+}
+
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]
struct PathItem {
path_type: PathType,
#[allow(dead_code)]
pub type Error = Box<dyn std::error::Error>;
+#[allow(dead_code)]
+pub const BIN_FILE: &str = "😀.bin";
+
/// File names for testing purpose
#[allow(dead_code)]
-pub static FILES: &[&str] = &["test.txt", "test.html", "index.html", "😀.bin"];
+pub static FILES: &[&str] = &["test.txt", "test.html", "index.html", BIN_FILE];
/// Directory names for testing directory don't exist
#[allow(dead_code)]
pub fn tmpdir() -> TempDir {
let tmpdir = assert_fs::TempDir::new().expect("Couldn't create a temp dir for tests");
for file in FILES {
- tmpdir
- .child(file)
- .write_str(&format!("This is {file}"))
- .expect("Couldn't write to file");
+ if *file == BIN_FILE {
+ tmpdir
+ .child(file)
+ .write_binary(b"bin\0\0123")
+ .expect("Couldn't write to file");
+ } else {
+ tmpdir
+ .child(file)
+ .write_str(&format!("This is {file}"))
+ .expect("Couldn't write to file");
+ }
}
for directory in DIRECTORIES {
if *directory == DIR_ASSETS {
if *directory == DIR_NO_INDEX && *file == "index.html" {
continue;
}
- tmpdir
- .child(format!("{directory}{file}"))
- .write_str(&format!("This is {directory}{file}"))
- .expect("Couldn't write to file");
+ if *file == BIN_FILE {
+ tmpdir
+ .child(format!("{directory}{file}"))
+ .write_binary(b"bin\0\0123")
+ .expect("Couldn't write to file");
+ } else {
+ tmpdir
+ .child(format!("{directory}{file}"))
+ .write_str(&format!("This is {directory}{file}"))
+ .expect("Couldn't write to file");
+ }
}
}
}
mod fixtures;
mod utils;
-use fixtures::{server, Error, TestServer};
+use fixtures::{server, Error, TestServer, BIN_FILE};
use rstest::rstest;
use serde_json::Value;
+use utils::retrive_edit_file;
#[rstest]
fn get_dir(server: TestServer) -> Result<(), Error> {
#[rstest]
fn get_dir_search2(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
- let resp = reqwest::blocking::get(format!("{}?q={}", server.url(), "😀.bin"))?;
+ let resp = reqwest::blocking::get(format!("{}?q={BIN_FILE}", server.url()))?;
assert_eq!(resp.status(), 200);
let paths = utils::retrieve_index_paths(&resp.text()?);
assert!(!paths.is_empty());
for p in paths {
- assert!(p.contains("😀.bin"));
+ assert!(p.contains(BIN_FILE));
}
Ok(())
}
Ok(())
}
+#[rstest]
+fn get_file_edit(server: TestServer) -> Result<(), Error> {
+ let resp = fetch!(b"GET", format!("{}index.html?edit", server.url())).send()?;
+ assert_eq!(resp.status(), 200);
+ let editable = retrive_edit_file(&resp.text().unwrap()).unwrap();
+ assert!(editable);
+ Ok(())
+}
+
+#[rstest]
+fn get_file_edit_bin(server: TestServer) -> Result<(), Error> {
+ let resp = fetch!(b"GET", format!("{}{BIN_FILE}?edit", server.url())).send()?;
+ assert_eq!(resp.status(), 200);
+ let editable = retrive_edit_file(&resp.text().unwrap()).unwrap();
+ assert!(!editable);
+ Ok(())
+}
+
#[rstest]
fn head_file_404(server: TestServer) -> Result<(), Error> {
let resp = fetch!(b"HEAD", format!("{}404", server.url())).send()?;
mod fixtures;
mod utils;
-use fixtures::{server, Error, TestServer, DIR_NO_FOUND, DIR_NO_INDEX, FILES};
+use fixtures::{server, Error, TestServer, BIN_FILE, DIR_NO_FOUND, DIR_NO_INDEX, FILES};
use rstest::rstest;
#[rstest]
#[case(server(&["--render-try-index"] as &[&str]), false)]
#[case(server(&["--render-try-index", "--allow-search"] as &[&str]), true)]
fn render_try_index4(#[case] server: TestServer, #[case] searched: bool) -> Result<(), Error> {
- let resp = reqwest::blocking::get(format!("{}{}?q={}", server.url(), DIR_NO_INDEX, "😀.bin"))?;
+ let resp = reqwest::blocking::get(format!("{}{}?q={}", server.url(), DIR_NO_INDEX, BIN_FILE))?;
assert_eq!(resp.status(), 200);
let paths = utils::retrieve_index_paths(&resp.text()?);
- assert!(!paths.is_empty());
- assert_eq!(paths.iter().all(|v| v.contains("😀.bin")), searched);
+ assert_eq!(paths.iter().all(|v| v.contains(BIN_FILE)), searched);
Ok(())
}
}
#[allow(dead_code)]
-pub fn retrieve_index_paths(index: &str) -> IndexSet<String> {
- retrieve_index_paths_impl(index).unwrap_or_default()
-}
-
-#[allow(dead_code)]
-pub fn encode_uri(v: &str) -> String {
- let parts: Vec<_> = v.split('/').map(urlencoding::encode).collect();
- parts.join("/")
-}
-
-fn retrieve_index_paths_impl(index: &str) -> Option<IndexSet<String>> {
- let lines: Vec<&str> = index.lines().collect();
- let line = lines.iter().find(|v| v.contains("DATA ="))?;
- let line_col = line.find("DATA =").unwrap() + 6;
- let value: Value = line[line_col..].parse().ok()?;
+pub fn retrieve_index_paths(content: &str) -> IndexSet<String> {
+ let value = retrive_json(content).unwrap();
let paths = value
- .get("paths")?
- .as_array()?
+ .get("paths")
+ .unwrap()
+ .as_array()
+ .unwrap()
.iter()
.flat_map(|v| {
let name = v.get("name")?.as_str()?;
}
})
.collect();
- Some(paths)
+ paths
+}
+
+#[allow(dead_code)]
+pub fn retrive_edit_file(content: &str) -> Option<bool> {
+ let value = retrive_json(content)?;
+ let value = value.get("editable").unwrap();
+ Some(value.as_bool().unwrap())
+}
+
+#[allow(dead_code)]
+pub fn encode_uri(v: &str) -> String {
+ let parts: Vec<_> = v.split('/').map(urlencoding::encode).collect();
+ parts.join("/")
+}
+
+fn retrive_json(content: &str) -> Option<Value> {
+ let lines: Vec<&str> = content.lines().collect();
+ let line = lines.iter().find(|v| v.contains("DATA ="))?;
+ let line_col = line.find("DATA =").unwrap() + 6;
+ let value: Value = line[line_col..].parse().unwrap();
+ Some(value)
}