Download
This commit is contained in:
parent
dc13ef503c
commit
5bcc40d573
5 changed files with 105 additions and 12 deletions
10
README.md
10
README.md
|
|
@ -12,7 +12,9 @@ Early development: not usable yet.
|
||||||
* [x] Repo caching
|
* [x] Repo caching
|
||||||
* [x] Submission form
|
* [x] Submission form
|
||||||
* [x] Config
|
* [x] Config
|
||||||
* [ ] Repo serving
|
* [x] Repo serving
|
||||||
|
* [x] Allow download
|
||||||
|
* [x] Markdown rendering
|
||||||
* [ ] Replace words
|
* [ ] Replace words
|
||||||
* [ ] Abuse report
|
* [ ] Abuse report
|
||||||
* [ ] Admin tools
|
* [ ] Admin tools
|
||||||
|
|
@ -21,9 +23,11 @@ Early development: not usable yet.
|
||||||
* [ ] Expiration
|
* [ ] Expiration
|
||||||
* [ ] Manual removal
|
* [ ] Manual removal
|
||||||
* [ ] Security tests (zip bomb)
|
* [ ] Security tests (zip bomb)
|
||||||
* [ ] Allow download
|
* [ ] Security tests (URL encoding in links, HTML, HTTP headers)
|
||||||
* [ ] Markdown rendering
|
|
||||||
* [ ] Error pages
|
* [ ] Error pages
|
||||||
|
* [ ] Webdesign
|
||||||
|
* [ ] Compatibility with other forges
|
||||||
|
* [ ] Better logo (contribution welcome)
|
||||||
|
|
||||||
## Design choices
|
## Design choices
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,23 @@ impl Renderer {
|
||||||
hl_registry.link_grammars();
|
hl_registry.link_grammars();
|
||||||
Self { hl_registry }
|
Self { hl_registry }
|
||||||
}
|
}
|
||||||
pub fn render(&self, config: &Config, filetype: &str, content: &[u8], pretty: bool, repo_hash_str: &str, file_path: &str) -> String {
|
pub fn render(
|
||||||
|
&self,
|
||||||
|
config: &Config,
|
||||||
|
filetype: &str,
|
||||||
|
content: &[u8],
|
||||||
|
pretty: bool,
|
||||||
|
repo_hash_str: &str,
|
||||||
|
file_path: &str,
|
||||||
|
) -> String {
|
||||||
if config.image_extensions.contains_key(filetype) {
|
if config.image_extensions.contains_key(filetype) {
|
||||||
return format!(r#"<img alt="No description" src="/inline/{repo_hash_str}/{file_path}"/>"#)
|
return format!(
|
||||||
|
r#"<img alt="No description" src="/inline/{repo_hash_str}/{file_path}"/>"#
|
||||||
|
);
|
||||||
} else if config.video_extensions.contains_key(filetype) {
|
} else if config.video_extensions.contains_key(filetype) {
|
||||||
return format!(r#"<video src="/inline/{repo_hash_str}/{file_path}"></video>"#)
|
return format!(r#"<video src="/inline/{repo_hash_str}/{file_path}"></video>"#);
|
||||||
} else if config.audio_extensions.contains_key(filetype) {
|
} else if config.audio_extensions.contains_key(filetype) {
|
||||||
return format!(r#"<audio src="/inline/{repo_hash_str}/{file_path}"></audio>"#)
|
return format!(r#"<audio src="/inline/{repo_hash_str}/{file_path}"></audio>"#);
|
||||||
}
|
}
|
||||||
let Ok(content) = str::from_utf8(content) else {
|
let Ok(content) = str::from_utf8(content) else {
|
||||||
return String::from("Cannot render file as it is not valid UTF-8.");
|
return String::from("Cannot render file as it is not valid UTF-8.");
|
||||||
|
|
|
||||||
|
|
@ -161,16 +161,25 @@ pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
.to_string();
|
.to_string();
|
||||||
file_lang.make_ascii_lowercase();
|
file_lang.make_ascii_lowercase();
|
||||||
|
|
||||||
html = renderer.render(config, &file_lang, &file_content, true, repo_hash_str, &served_file.path);
|
html = renderer.render(
|
||||||
|
config,
|
||||||
|
&file_lang,
|
||||||
|
&file_content,
|
||||||
|
true,
|
||||||
|
repo_hash_str,
|
||||||
|
&served_file.path,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// TODO something?
|
// TODO something?
|
||||||
}
|
}
|
||||||
|
|
||||||
let template = crate::templates::Repo {
|
let template = crate::templates::Repo {
|
||||||
|
current_entry: served_entry.into(),
|
||||||
content: html.clone(),
|
content: html.clone(),
|
||||||
root,
|
root,
|
||||||
path: conn.path().split('/').map(|s| s.to_string()).collect(),
|
path: conn.path().split('/').map(|s| s.to_string()).collect(),
|
||||||
title: repo_metadata.title,
|
title: repo_metadata.title,
|
||||||
|
repo_hash: repo_hash_str.into(),
|
||||||
};
|
};
|
||||||
template.render().unwrap()
|
template.render().unwrap()
|
||||||
}
|
}
|
||||||
|
|
@ -189,12 +198,21 @@ pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
.to_string();
|
.to_string();
|
||||||
file_lang.make_ascii_lowercase();
|
file_lang.make_ascii_lowercase();
|
||||||
|
|
||||||
let html = renderer.render(config, &file_lang, &file_content, true, repo_hash_str, &served_file.path);
|
let html = renderer.render(
|
||||||
|
config,
|
||||||
|
&file_lang,
|
||||||
|
&file_content,
|
||||||
|
true,
|
||||||
|
repo_hash_str,
|
||||||
|
&served_file.path,
|
||||||
|
);
|
||||||
let template = crate::templates::Repo {
|
let template = crate::templates::Repo {
|
||||||
|
current_entry: served_entry.into(),
|
||||||
content: html.clone(),
|
content: html.clone(),
|
||||||
root,
|
root,
|
||||||
path: conn.path().split('/').map(|s| s.to_string()).collect(),
|
path: conn.path().split('/').map(|s| s.to_string()).collect(),
|
||||||
title: repo_metadata.title,
|
title: repo_metadata.title,
|
||||||
|
repo_hash: repo_hash_str.into(),
|
||||||
};
|
};
|
||||||
template.render().unwrap()
|
template.render().unwrap()
|
||||||
}
|
}
|
||||||
|
|
@ -228,9 +246,7 @@ pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
};
|
};
|
||||||
|
|
||||||
match served_entry {
|
match served_entry {
|
||||||
templates::EntryRef::Directory(_) => {
|
templates::EntryRef::Directory(_) => conn.with_status(404),
|
||||||
conn.with_status(404)
|
|
||||||
}
|
|
||||||
templates::EntryRef::File(served_file) => {
|
templates::EntryRef::File(served_file) => {
|
||||||
let Some(extension) = served_file.name.rsplit('.').next() else {
|
let Some(extension) = served_file.name.rsplit('.').next() else {
|
||||||
return conn.with_status(404);
|
return conn.with_status(404);
|
||||||
|
|
@ -255,6 +271,51 @@ pub fn make_router(config: &'static Config) -> impl Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.get("/download/:hash/*", move |conn: Conn| async move {
|
||||||
|
let Some(repo_hash_str) = conn.param("hash") else {
|
||||||
|
return conn.with_status(401);
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((_repo_metadata, root)) = metadata_cache
|
||||||
|
.fetch(repo_hash_str.to_string(), |key| {
|
||||||
|
fetch_metadata(config, &key)
|
||||||
|
})
|
||||||
|
else {
|
||||||
|
return conn.with_status(404);
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(served_entry) = root.find(conn.path().split('/')) else {
|
||||||
|
return conn.with_status(404);
|
||||||
|
};
|
||||||
|
|
||||||
|
match served_entry {
|
||||||
|
templates::EntryRef::Directory(_) => conn.with_status(404),
|
||||||
|
templates::EntryRef::File(served_file) => {
|
||||||
|
let Some(file_content) =
|
||||||
|
fetch_file(config, repo_hash_str, &served_file.hash)
|
||||||
|
else {
|
||||||
|
return conn.with_status(500);
|
||||||
|
};
|
||||||
|
let disposition = if served_file.name.is_ascii() {
|
||||||
|
format!(
|
||||||
|
r#"attachment; filename="{}""#,
|
||||||
|
askama::filters::urlencode(&served_file.name).unwrap().0
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"attachment; filename*=UTF-8''{}",
|
||||||
|
askama::filters::urlencode(&served_file.name).unwrap().0
|
||||||
|
)
|
||||||
|
};
|
||||||
|
conn.with_response_header(
|
||||||
|
"Access-Control-Allow-Origin",
|
||||||
|
config.origin.as_str(),
|
||||||
|
)
|
||||||
|
.with_response_header("Content-Disposition", disposition)
|
||||||
|
.ok(file_content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
.get("/e/:secret", |conn: Conn| async move {
|
.get("/e/:secret", |conn: Conn| async move {
|
||||||
conn.ok(crate::templates::Home {}.render().unwrap())
|
conn.ok(crate::templates::Home {}.render().unwrap())
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ pub struct Repo {
|
||||||
pub path: Vec<String>,
|
pub path: Vec<String>,
|
||||||
/// Repository title
|
/// Repository title
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
/// Repository id
|
||||||
|
pub repo_hash: String,
|
||||||
|
/// Served entry
|
||||||
|
pub current_entry: Entry,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Template)]
|
#[derive(Clone, Template)]
|
||||||
|
|
@ -53,6 +57,15 @@ pub enum EntryRef<'a> {
|
||||||
File(&'a File),
|
File(&'a File),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<EntryRef<'a>> for Entry {
|
||||||
|
fn from(value: EntryRef<'a>) -> Self {
|
||||||
|
match value {
|
||||||
|
EntryRef::Directory(dir) => Entry::Directory(dir.clone()),
|
||||||
|
EntryRef::File(file) => Entry::File(file.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@ html, body {
|
||||||
<span>
|
<span>
|
||||||
{% for element in path %}/{{ element }}{% endfor %}
|
{% for element in path %}/{{ element }}{% endfor %}
|
||||||
</span>
|
</span>
|
||||||
|
{% match current_entry %}
|
||||||
|
{% when Entry::Directory(dir) %}
|
||||||
|
{% when Entry::File(file) %}
|
||||||
|
<a href="/download/{{ repo_hash|safe }}/{{ file.path }}">Download file</a>
|
||||||
|
{% endmatch %}
|
||||||
<div id="content">
|
<div id="content">
|
||||||
{{ content|safe }}
|
{{ content|safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue