Compare commits

...

30 Commits

Author SHA1 Message Date
Dániel Szabó
f55a5eba96
Merge pull request #43 from FoxFromDarkness66/patch-3
Update AUR installation method
2022-08-06 22:55:15 +01:00
FixFromDarkness
011cc25490
Update AUR installation method 2022-08-05 12:25:05 +03:00
Dániel Szabó
d44a3081bc
Delete FUNDING.yml 2022-08-02 22:36:06 +01:00
Dániel Szabó
51f7f54be7
Create SECURITY.md 2022-08-01 10:07:42 +01:00
Daniel Szabo
a3fc97a460 bump version number 2022-07-31 22:06:41 +01:00
Daniel Szabo
05941f0d6f Isolate pasta uploads from database.json by moving them into pasta_data/public/ 2022-07-31 21:49:36 +01:00
Daniel Szabo
7b4cd7c26e Implement upload filename sanitisation 2022-07-31 21:31:35 +01:00
Daniel Szabo
f54d5bd780 Add pasta ID + pasta URL to pasta page. Fix #36 2022-07-31 19:41:19 +01:00
Daniel Szabo
435c07d75e Implement manual deletion behaviour and fix #35 2022-07-31 19:18:07 +01:00
Dániel Szabó
cc09d1b529
Merge pull request #38 from uniqueNullptr2/uniqueNullptr2-fix-title-in-template
fix usage of title arg in template
2022-07-31 18:59:37 +01:00
uniqueNullptr2
60c3a1f9ac fix usage of title arg in template 2022-07-25 15:45:07 +02:00
Dániel Szabó
d4d94b61da
Merge pull request #34 from uniqueNullptr2/uniquenullptr2-add-config-from-env
add configuration from env to all clap options
2022-07-25 14:04:26 +01:00
Dániel Szabó
9053211904
Merge pull request #31 from dvdsk/file-size
Adds file size to pasta with an attachment
2022-07-25 13:39:00 +01:00
uniqueNullptr2
35a512680c fix mistype of syntax highlight option 2022-07-20 09:13:31 +02:00
uniqueNullptr2
bcd620ed43 add configuration from env to all clap options 2022-07-20 08:50:23 +02:00
Dániel Szabó
556f4e87df
Merge pull request #33 from amnesiacsardine/patch-1
Update to README.MD regarding Dockerfile
2022-07-18 00:05:56 +01:00
amnesiacsardine
fa88bce917
Update README.MD
Added how to pass command line arguments in the Dockerfile
2022-07-16 13:22:31 +02:00
Dániel Szabó
a5d326b679
Merge pull request #30 from dvdsk/master
Fixes #29 and displays pasta list in local timezone
2022-07-15 22:51:26 +01:00
Dániel Szabó
465873e095
Merge pull request #28 from FoxFromDarkness66/patch-2
Add bind address CL option
2022-07-15 22:47:39 +01:00
dvdsk
39233e9447
fixes #6 adding the size of the attached file 2022-07-14 01:08:13 +02:00
dvdsk
738e036cb5
pasta times are in systems local timezone 2022-07-13 23:55:28 +02:00
dvdsk
de2cc48f88
fixes #29 (time formating) 2022-07-13 23:54:48 +02:00
Dániel Szabó
0687f44137
Merge pull request #27 from FoxFromDarkness66/patch-1
Add AUR installation method
2022-07-07 23:12:48 +01:00
FixFromDarkness
fec933c5ec * Revert default address to 0.0.0.0 due to docker usage & compatibility
* Add --bind option to readme & change some examples
2022-07-07 20:08:29 +03:00
FixFromDarkness
cc2dd1e1fe * Add --bind option
* Changed default bind address to localhost
* Fix wrong log text about binding address
2022-07-07 19:45:31 +03:00
FixFromDarkness
85ed1b2b92
Add AUR installation method
AUR is Arch User Repository, "community-driven repository for Arch users. It contains package descriptions (PKGBUILDs) that allow you to compile a package from source with makepkg and then install it via pacman." 
I've made an AUR package that will allow any arch-based distro user to install and update microban to the latest version without manual version cheking&compiling. It will be easier for them to find it if you add information about this in the readme
2022-07-07 19:15:26 +03:00
Dániel Szabó
73ec59ccda
Merge pull request #21 from Arizard/master
Updates Dockerfile and adds docker build instructions
2022-06-27 20:58:01 +01:00
Arie
f5b9036a2a Uses volume absolute path, changes wording 2022-06-27 10:52:37 +10:00
Arie
0d43f2f60a Updates README with docker build instructions 2022-06-27 10:39:32 +10:00
Arie
aa9246da4e Removes COPY instruction for static directory 2022-06-27 10:37:25 +10:00
14 changed files with 224 additions and 96 deletions

13
.github/FUNDING.yml vendored
View File

@ -1,13 +0,0 @@
# These are supported funding model platforms
github: szabodanika
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: dani_sz
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@ -1,6 +1,6 @@
[package]
name="microbin"
version="1.0.2"
version="1.1.0"
edition="2021"
authors = ["Daniel Szabo <daniel.szabo99@outlook.com>"]
license = "BSD-3-Clause"
@ -18,12 +18,13 @@ actix-web="4"
actix-files="0.6.0"
serde={ version = "1.0", features = ["derive"] }
serde_json = "1.0.80"
bytesize = { version = "1.1", features = ["serde"] }
askama="0.10"
askama-filters={ version = "0.1.3", features = ["chrono"] }
chrono="0.4.19"
rand="0.8.5"
linkify="0.8.1"
clap={ version = "3.1.12", features = ["derive"] }
clap={ version = "3.1.12", features = ["derive", "env"] }
actix-multipart = "0.4.0"
futures = "0.3"
sanitize-filename = "0.3.0"

View File

@ -19,8 +19,5 @@ WORKDIR /usr/local/bin
# copy built exacutable
COPY --from=builder /usr/src/microbin/target/release/microbin /usr/local/bin/microbin
# copy /static folder containing the stylesheets
COPY --from=builder /usr/src/microbin/static /usr/local/bin/static
# run the binary
CMD ["microbin"]
CMD ["microbin"]

View File

@ -105,11 +105,14 @@ Remember, MicroBin will create your database and file storage wherever you execu
`cd ~/microbin/`
`microbin --port 8080 --highlightsyntax --editable`
`microbin --highlightsyntax --editable`
### From AUR (for any Arch-based distro)
Install `microbin` package from AUR and start/enable microbin systemd service. Systemd will start server on 127.0.0.1:8080 with almost all features enabled, but this can be changed in `/etc/microbin.conf`.
### Building MicroBin
Simply clone the repository, build it with `cargo build --release` and run the `microbin` executable in the created `target/release/` directory. It will start on port 8080. You can change the port with `-p` or `--port` CL arguments. For other arguments see [the Wiki](https://github.com/szabodanika/microbin/wiki).
Simply clone the repository, build it with `cargo build --release` and run the `microbin` executable in the created `target/release/` directory. It will start listening on 0.0.0.0:8080. You can change the port or bind address with CL arguments `-p (--port)` or `-b (--bind)` respectively . For other arguments see [the Wiki](https://github.com/szabodanika/microbin/wiki).
```
git clone https://github.com/szabodanika/microbin.git
@ -118,6 +121,49 @@ cargo build --release
./target/release/microbin -p 80
```
### Building Docker Image
MicroBin includes a Dockerfile. To build the image, follow these steps:
```
git clone https://github.com/szabodanika/microbin.git
cd microbin
docker build -t microbin-docker .
```
Then, for `docker compose` you can repurpose the following example in your compose file:
```
services:
paste:
image: microbin-docker
restart: always
ports:
- "80:8080"
volumes:
- ./microbin-data:/usr/local/bin/pasta_data
```
To pass command line arguments you must edit the Dockerfile and change the CMD line. In this example we add the syntax highlighting option and enable private pastas:
```
CMD ["microbin", "--highlightsyntax", "--private"]
```
You then need to rebuild the image and recreate your container.
**Note:** If you are getting the following error about domain name resolution:
```
warning: spurious network error (2 tries remaining): failed to resolve address for github.com: Temporary failure in name resolution; class=Net (12)
warning: spurious network error (1 tries remaining): failed to resolve address for github.com: Temporary failure in name resolution; class=Net (12)
```
You might need to run `docker build` with the `--network` option:
```
docker build --network host -t microbin-docker .
```
### MicroBin as a service
To install it as a service on your Linux machine, create a file called `/etc/systemd/system/microbin.service`, paste this into it with the `[username]` and `[path to installation directory]` replaced with the actual values. If you installed MicroBin from cargo, your executable will be in your cargo directory, e.g. `/Users/daniel/.cargo/bin/microbin`.
@ -247,6 +293,13 @@ Default value: 8080
Sets the port for the server will be listening on.
### -b, --bind [ADDRESS]
Default value: 0.0.0.0
Sets the bind address for the server will be listening on. Both ipv4 and ipv6 are supported.
### --private
Enables private pastas. Adds a new checkbox to make your pasta private, which then won't show up on the pastalist page. With the URL to your pasta, it will still be accessible.

19
SECURITY.md Normal file
View File

@ -0,0 +1,19 @@
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 1.1.* | :white_check_mark: |
| < 1.1.0 | :x: |
## Reporting a Vulnerability
Security vulnerabilities can be reported directly to
the developer/maintainer at d@szab.eu.
Sensitive information may be GPG encrypted with my public key available at
https://szab.eu/assets/files/daniel-szabo-pub.asc.

View File

@ -1,3 +1,4 @@
use std::net::IpAddr;
use clap::Parser;
use lazy_static::lazy_static;
@ -8,51 +9,54 @@ lazy_static! {
#[derive(Parser, Debug, Clone)]
#[clap(author, version, about, long_about = None)]
pub struct Args {
#[clap(long)]
#[clap(long, env="MICROBIN_AUTH_USERNAME")]
pub auth_username: Option<String>,
#[clap(long)]
#[clap(long, env="MICROBIN_AUTH_PASSWORD")]
pub auth_password: Option<String>,
#[clap(long)]
#[clap(long, env="MICROBIN_EDITABLE")]
pub editable: bool,
#[clap(long)]
#[clap(long, env="MICROBIN_FOOTER_TEXT")]
pub footer_text: Option<String>,
#[clap(long)]
#[clap(long, env="MICROBIN_HIDE_FOOTER")]
pub hide_footer: bool,
#[clap(long)]
#[clap(long, env="MICROBIN_HIDE_HEADER")]
pub hide_header: bool,
#[clap(long)]
#[clap(long, env="MICROBIN_HIDE_LOGO")]
pub hide_logo: bool,
#[clap(long)]
#[clap(long, env="MICROBIN_NO_LISTING")]
pub no_listing: bool,
#[clap(long)]
#[clap(long, env="MICROBIN_HIGHLIGHTSYNTAX")]
pub highlightsyntax: bool,
#[clap(short, long, default_value_t = 8080)]
pub port: u32,
#[clap(short, long, env="MICROBIN_PORT", default_value_t = 8080)]
pub port: u16,
#[clap(long)]
#[clap(short, long, env="MICROBIN_BIND", default_value_t = IpAddr::from([0, 0, 0, 0]))]
pub bind: IpAddr,
#[clap(long, env="MICROBIN_PRIVATE")]
pub private: bool,
#[clap(long)]
#[clap(long, env="MICROBIN_PURE_HTML")]
pub pure_html: bool,
#[clap(long)]
#[clap(long, env="MICROBIN_READONLY")]
pub readonly: bool,
#[clap(long)]
#[clap(long, env="MICROBIN_TITLE")]
pub title: Option<String>,
#[clap(short, long, default_value_t = 1)]
#[clap(short, long, env="MICROBIN_THREADS", default_value_t = 1)]
pub threads: u8,
#[clap(long)]
#[clap(long, env="MICROBIN_WIDE")]
pub wide: bool,
}
}

View File

@ -1,11 +1,14 @@
use crate::dbio::save_to_file;
use crate::pasta::PastaFile;
use crate::util::animalnumbers::to_animal_names;
use crate::util::misc::is_valid_url;
use crate::{AppState, Pasta, ARGS};
use actix_multipart::Multipart;
use actix_web::{get, web, Error, HttpResponse, Responder};
use askama::Template;
use bytesize::ByteSize;
use futures::TryStreamExt;
use log::warn;
use rand::Rng;
use std::io::Write;
use std::time::{SystemTime, UNIX_EPOCH};
@ -46,7 +49,7 @@ pub async fn create(
let mut new_pasta = Pasta {
id: rand::thread_rng().gen::<u16>() as u64,
content: String::from("No Text Content"),
file: String::from("no-file"),
file: None,
extension: String::from(""),
private: false,
editable: false,
@ -103,27 +106,41 @@ pub async fn create(
continue;
}
"file" => {
let content_disposition = field.content_disposition();
let path = field.content_disposition().get_filename();
let filename = match content_disposition.get_filename() {
let path = match path {
Some("") => continue,
Some(filename) => filename.replace(' ', "_").to_string(),
Some(p) => p,
None => continue,
};
std::fs::create_dir_all(format!("./pasta_data/{}", &new_pasta.id_as_animals()))
let mut file = match PastaFile::from_unsanitized(&path) {
Ok(f) => f,
Err(e) => {
warn!("Unsafe file name: {e:?}");
continue;
}
};
std::fs::create_dir_all(format!("./pasta_data/public/{}", &new_pasta.id_as_animals()))
.unwrap();
let filepath = format!("./pasta_data/{}/{}", &new_pasta.id_as_animals(), &filename);
new_pasta.file = filename;
let filepath = format!(
"./pasta_data/public/{}/{}",
&new_pasta.id_as_animals(),
&file.name()
);
let mut f = web::block(|| std::fs::File::create(filepath)).await??;
let mut size = 0;
while let Some(chunk) = field.try_next().await? {
size += chunk.len();
f = web::block(move || f.write_all(&chunk).map(|_| f)).await??;
}
file.size = ByteSize::b(size as u64);
new_pasta.file = Some(file);
new_pasta.pasta_type = String::from("text");
}
_ => {}

View File

@ -2,10 +2,12 @@ use actix_web::{get, web, HttpResponse};
use crate::args::ARGS;
use crate::endpoints::errors::ErrorTemplate;
use crate::pasta::PastaFile;
use crate::util::animalnumbers::to_u64;
use crate::util::misc::remove_expired;
use crate::AppState;
use askama::Template;
use std::fs;
#[get("/remove/{id}")]
pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
@ -19,10 +21,22 @@ pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRes
let id = to_u64(&*id.into_inner()).unwrap_or(0);
remove_expired(&mut pastas);
for (i, pasta) in pastas.iter().enumerate() {
if pasta.id == id {
// remove the file itself
if let Some(PastaFile { name, .. }) = &pasta.file {
if fs::remove_file(format!("./pasta_data/public/{}/{}", pasta.id_as_animals(), name))
.is_err()
{
log::error!("Failed to delete file {}!", name)
}
// and remove the containing directory
if fs::remove_dir(format!("./pasta_data/public/{}/", pasta.id_as_animals())).is_err() {
log::error!("Failed to delete directory {}!", name)
}
}
// remove it from in-memory pasta list
pastas.remove(i);
return HttpResponse::Found()
.append_header(("Location", "/pastalist"))
@ -30,6 +44,8 @@ pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRes
}
}
remove_expired(&mut pastas);
HttpResponse::Ok()
.content_type("text/html")
.body(ErrorTemplate { args: &ARGS }.render().unwrap())

View File

@ -58,15 +58,16 @@ async fn main() -> std::io::Result<()> {
.init();
log::info!(
"MicroBin starting on http://127.0.0.1:{}",
"MicroBin starting on http://{}:{}",
ARGS.bind.to_string(),
ARGS.port.to_string()
);
match fs::create_dir_all("./pasta_data") {
match fs::create_dir_all("./pasta_data/public") {
Ok(dir) => dir,
Err(error) => {
log::error!("Couldn't create data directory ./pasta_data: {:?}", error);
panic!("Couldn't create data directory ./pasta_data: {:?}", error);
log::error!("Couldn't create data directory ./pasta_data/public/: {:?}", error);
panic!("Couldn't create data directory ./pasta_data/public/: {:?}", error);
}
};
@ -86,7 +87,7 @@ async fn main() -> std::io::Result<()> {
.service(edit::get_edit)
.service(edit::post_edit)
.service(static_resources::static_resources)
.service(actix_files::Files::new("/file", "./pasta_data"))
.service(actix_files::Files::new("/file", "./pasta_data/public/"))
.service(web::resource("/upload").route(web::post().to(create::create)))
.default_service(web::route().to(errors::not_found))
.wrap(middleware::Logger::default())
@ -97,7 +98,7 @@ async fn main() -> std::io::Result<()> {
HttpAuthentication::basic(util::auth::auth_validator),
))
})
.bind(format!("0.0.0.0:{}", ARGS.port.to_string()))?
.bind((ARGS.bind, ARGS.port))?
.workers(ARGS.threads as usize)
.run()
.await

View File

@ -1,16 +1,39 @@
use std::fmt;
use chrono::{DateTime, Datelike, NaiveDateTime, Timelike, Utc};
use bytesize::ByteSize;
use chrono::{Datelike, Local, TimeZone, Timelike};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::Path;
use crate::util::animalnumbers::to_animal_names;
use crate::util::syntaxhighlighter::html_highlight;
#[derive(Serialize, Deserialize, PartialEq, Eq)]
pub struct PastaFile {
pub name: String,
pub size: ByteSize,
}
impl PastaFile {
pub fn from_unsanitized(path: &str) -> Result<Self, &'static str> {
let path = Path::new(path);
let name = path.file_name().ok_or("Path did not contain a file name")?;
let name = name.to_string_lossy().replace(' ', "_");
Ok(Self {
name,
size: ByteSize::b(0),
})
}
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Serialize, Deserialize)]
pub struct Pasta {
pub id: u64,
pub content: String,
pub file: String,
pub file: Option<PastaFile>,
pub extension: String,
pub private: bool,
pub editable: bool,
@ -25,9 +48,9 @@ impl Pasta {
}
pub fn created_as_string(&self) -> String {
let date = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.created, 0), Utc);
let date = Local.timestamp(self.created, 0);
format!(
"{:02}-{:02} {}:{}",
"{:02}-{:02} {:02}:{:02}",
date.month(),
date.day(),
date.hour(),
@ -39,10 +62,9 @@ impl Pasta {
if self.expiration == 0 {
String::from("Never")
} else {
let date =
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(self.expiration, 0), Utc);
let date = Local.timestamp(self.expiration, 0);
format!(
"{:02}-{:02} {}:{}",
"{:02}-{:02} {:02}:{:02}",
date.month(),
date.day(),
date.hour(),

View File

@ -22,20 +22,22 @@ pub fn remove_expired(pastas: &mut Vec<Pasta>) {
true
} else {
// remove the file itself
match fs::remove_file(format!("./pasta_data/{}/{}", p.id_as_animals(), p.file)) {
Ok(_) => {}
Err(_) => {
log::error!("Failed to delete file {}!", p.file)
if let Some(file) = &p.file {
if fs::remove_file(format!(
"./pasta_data/public/{}/{}",
p.id_as_animals(),
file.name()
))
.is_err()
{
log::error!("Failed to delete file {}!", file.name())
}
// and remove the containing directory
if fs::remove_dir(format!("./pasta_data/public/{}/", p.id_as_animals())).is_err() {
log::error!("Failed to delete directory {}!", file.name())
}
}
// and remove the containing directory
match fs::remove_dir(format!("./pasta_data/{}/", p.id_as_animals())) {
Ok(_) => {}
Err(_) => {
log::error!("Failed to delete directory {}!", p.file)
}
}
// remove
false
}
});

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
{% if args.footer_text.as_ref().is_none() %}
{% if args.title.as_ref().is_none() %}
<title>MicroBin</title>
{%- else %}
<title>{{ args.title.as_ref().unwrap() }}</title>
@ -42,7 +42,7 @@
<i><span style="font-size:2.2rem; margin-right:1rem">μ</span></i>
{%- endif %}
{% if args.footer_text.as_ref().is_none() %}
{% if args.title.as_ref().is_none() %}
MicroBin
{%- else %}
{{ args.title.as_ref().unwrap() }}

View File

@ -1,19 +1,28 @@
{% include "header.html" %}
<a style="margin-right: 0.5rem" href="/raw/{{pasta.id_as_animals()}}">Raw Text Content</a>
{% if pasta.file != "no-file" %}
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file}}">Attached file
'{{pasta.file}}'</a>
{%- endif %}
{% if pasta.editable %}
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/edit/{{pasta.id_as_animals()}}">Edit</a>
{%- endif %}
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/remove/{{pasta.id_as_animals()}}">Remove</a>
{% if args.highlightsyntax %}
<pre><code>{{pasta.content_syntax_highlighted()}}</code></pre>
{%- else %}
<pre><code>{{pasta.content_not_highlighted()}}</code></pre>
{%- endif %}
<div style="float: left">
<a style="margin-right: 0.5rem" href="/raw/{{pasta.id_as_animals()}}">Raw Text Content</a>
{% if pasta.file.is_some() %}
<a style="margin-right: 0.5rem; margin-left: 0.5rem"
href="/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}">
Attached file'{{pasta.file.as_ref().unwrap().name()}}' [{{pasta.file.as_ref().unwrap().size}}]
</a>
{%- endif %}
{% if pasta.editable %}
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/edit/{{pasta.id_as_animals()}}">Edit</a>
{%- endif %}
<a style="margin-right: 0.5rem; margin-left: 0.5rem" href="/remove/{{pasta.id_as_animals()}}">Remove</a>
</div>
<div style="float: right">
<a href="/pasta/{{pasta.id_as_animals()}}"><i>{{pasta.id_as_animals()}}</i></a>
</div>
<br>
<div style="clear: both;">
{% if args.highlightsyntax %}
<pre><code>{{pasta.content_syntax_highlighted()}}</code></pre>
{%- else %}
<pre><code>{{pasta.content_not_highlighted()}}</code></pre>
{%- endif %}
</div>
<style>
code-line {
counter-increment: listing;

View File

@ -48,8 +48,8 @@
</td>
<td>
<a style="margin-right:1rem" href="/raw/{{pasta.id_as_animals()}}">Raw</a>
{% if pasta.file != "no-file" %}
<a style="margin-right:1rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file}}">File</a>
{% if pasta.file.is_some() %}
<a style="margin-right:1rem" href="/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}">File</a>
{%- endif %}
{% if pasta.editable %}
<a style="margin-right:1rem" href="/edit/{{pasta.id_as_animals()}}">Edit</a>