Compare commits

..

No commits in common. "main" and "xml-parse" have entirely different histories.

6 changed files with 76 additions and 354 deletions

2
.gitignore vendored
View file

@ -1,5 +1,3 @@
/target /target
.env .env
Cargo.lock Cargo.lock
scratchpad/
*.bak

119
Cargo.lock generated
View file

@ -11,12 +11,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "array_tool"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f8cb5d814eb646a863c4f24978cff2880c4be96ad8cde2c0f0678732902e271"
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -86,26 +80,6 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]] [[package]]
name = "dotenv" name = "dotenv"
version = "0.15.0" version = "0.15.0"
@ -240,15 +214,6 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -600,18 +565,12 @@ dependencies = [
name = "publicise-rs" name = "publicise-rs"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"array_tool",
"dotenv", "dotenv",
"env_logger", "env_logger",
"log", "log",
"reqwest", "reqwest",
"serde",
"tokio", "tokio",
"toml", "xml-rs",
"url",
"xdg",
"yaserde",
"yaserde_derive",
] ]
[[package]] [[package]]
@ -672,16 +631,6 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.5.4" version = "1.5.4"
@ -790,23 +739,9 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.136" version = "1.0.131"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.136"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
@ -966,15 +901,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.1" version = "0.3.1"
@ -1022,12 +948,6 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.2" version = "0.2.2"
@ -1184,41 +1104,8 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "xdg"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6"
dependencies = [
"dirs",
]
[[package]] [[package]]
name = "xml-rs" name = "xml-rs"
version = "0.8.4" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
[[package]]
name = "yaserde"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2776ec5bb20e76d89268e87e1ea66c078b94f55e9771e4d648adda3019f87fc"
dependencies = [
"log",
"xml-rs",
]
[[package]]
name = "yaserde_derive"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c0b0a4701f203ebaecce4971a6bb8575aa07b617bdc39ddfc6ffeff3a38530d"
dependencies = [
"heck",
"log",
"proc-macro2",
"quote",
"syn",
"xml-rs",
]

View file

@ -6,15 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
array_tool = "1.0.3"
dotenv = "0.15.0" dotenv = "0.15.0"
env_logger = "0.8.4" env_logger = "0.8.4"
log = "0.4.0" log = "0.4.0"
reqwest = { version = "0.11", features = ["json"] } reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0.136" , features = ["derive"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
toml = "0.5.8" xml-rs = "0.8.4"
url = "2.2.2"
xdg = "2.4.1"
yaserde = "0.7.1"
yaserde_derive = "0.7.1"

13
LICENSE
View file

@ -1,13 +0,0 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

View file

@ -1,5 +0,0 @@
### publicise-rs
_Publicise_ is an application which traverses a Nextcloud folder and creates a public share for each file.
The application was originally devised as a way to share photos with people without Instagram accounts, and as an exploration of Rust.

View file

@ -1,132 +1,29 @@
extern crate xdg; extern crate xml;
use env_logger::Env; #[allow(unused)]
use log::debug; // use dotenv::dotenv;
use env_logger::{ Env, Target };
#[allow(unused)]
use log::{debug, info, log_enabled, warn, error};
use reqwest; use reqwest;
use serde::Deserialize; #[allow(unused)]
use std::collections::HashMap; use std::io::{self, BufReader, Write};
use std::fs; #[allow(unused)]
use std::process::Command; use xml::reader::{EventReader, XmlEvent};
use std::str;
use toml;
use url::Url;
use yaserde_derive::YaDeserialize;
use yaserde::de::from_str;
#[derive(Debug, Deserialize)]
pub struct Config { #[allow(unused)]
credentials: Credentials, fn publicise() {
paths: NextcloudPaths,
} }
#[derive(Debug, Deserialize)] async fn get_folder_contents(password: String, url_tail: &str) -> Result<String, Box<dyn std::error::Error>> {
pub struct Credentials { debug!("Entering: get_folder_contents()");
username: String, let root_url = "https://theadamcooper.com";
password: Option<String>,
password_script: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct NextcloudPaths {
root: String,
target: String,
}
#[derive(Default, PartialEq, Debug, YaDeserialize)]
#[yaserde(
rename = "multistatus",
prefix = "d",
namespace = "d: DAV:",
namespace = "nc: http://nextcloud.org/ns",
namespace = "oc: http://owncloud.org/ns",
namespace = "s: http://sabredav.org/ns"
)]
pub struct Multistatus {
#[yaserde(prefix = "d")]
response: Vec<NextcloudResponse>,
}
#[derive(Default, PartialEq, Debug, YaDeserialize)]
#[yaserde(rename = "response", prefix = "d", namespace = "d: DAV:")]
pub struct NextcloudResponse {
#[yaserde(prefix = "d")]
href: String,
#[yaserde(prefix = "d")]
propstat: Vec<Propstat>,
}
#[derive(Default, PartialEq, Debug, YaDeserialize)]
#[yaserde(rename = "propstat", prefix = "d", namespace = "d: DAV:")]
pub struct Propstat {
#[yaserde(prefix = "d")]
prop: Prop,
#[yaserde(prefix = "d")]
status: String,
}
#[derive(Default, PartialEq, Debug, YaDeserialize)]
#[yaserde(
rename = "prop",
namespace = "d: DAV:",
namespace = "oc: http://owncloud.org/ns"
)]
pub struct Prop {
#[yaserde(prefix = "d", rename = "getlastmodified")]
get_last_modified: String,
#[yaserde(prefix = "oc")]
permissions: String,
#[yaserde(prefix = "d", rename = "resourcetype")]
resource_type: ResourceType,
#[yaserde(prefix = "d", rename = "getetag")]
get_etag: String,
#[yaserde(prefix = "d", rename = "getcontentlength")]
get_content_length: u32,
#[yaserde(prefix = "d", rename = "getcontenttype")]
get_content_type: String,
#[yaserde(prefix = "oc", rename = "share-types")]
share_types: Vec<ShareType>,
}
#[derive(Default, PartialEq, Debug, YaDeserialize)]
#[yaserde(rename = "share-types", namespace = "oc: http://owncloud.org/ns")]
pub struct ShareType {
#[yaserde(rename = "share-type", prefix = "oc")]
share_type: u8,
}
#[derive(Default, PartialEq, Debug, YaDeserialize)]
#[yaserde(rename = "resourcetype", namespace = "d: DAV:")]
pub struct ResourceType {
#[yaserde(prefix = "d")]
collection: Vec<Collection>,
}
#[derive(Default, PartialEq, Debug, YaDeserialize)]
#[yaserde(rename = "collection", namespace = "d: DAV:")]
pub struct Collection {
#[yaserde(prefix = "d")]
collection: String,
}
fn init() -> Result<Config, Box<dyn std::error::Error>> {
debug!("[init] Entering function...");
let xdg_dirs = xdg::BaseDirectories::with_prefix("publicise-rs").unwrap();
let config_path = xdg_dirs.find_config_file("config.toml").unwrap();
let config_file_contents = fs::read_to_string(config_path).unwrap();
let config: Config = toml::from_str(&config_file_contents).unwrap();
debug!("[init] config: {:?}", config);
return Ok(config);
}
async fn get_folder_contents(url_tail: &str, config: &Config) -> Result<String, Box<dyn std::error::Error>> {
debug!("[get_folder_contents] Entering function...");
let root_url = Url::parse(&config.paths.root)?;
let method = reqwest::Method::from_bytes(b"PROPFIND").unwrap(); let method = reqwest::Method::from_bytes(b"PROPFIND").unwrap();
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let url = root_url.join(url_tail)?; let url = format!("{}{}", root_url, url_tail);
debug!("[get_folder_contents] url: {}", url); debug!("url: {}", url);
let body = String::from( let body = String::from(r#"<?xml version="1.0" encoding="UTF-8"?>
r#"<?xml version="1.0" encoding="UTF-8"?>
<d:propfind xmlns:d="DAV:"> <d:propfind xmlns:d="DAV:">
<d:prop xmlns:oc="http://owncloud.org/ns"> <d:prop xmlns:oc="http://owncloud.org/ns">
<d:getlastmodified/> <d:getlastmodified/>
@ -137,115 +34,79 @@ async fn get_folder_contents(url_tail: &str, config: &Config) -> Result<String,
<d:getetag/> <d:getetag/>
<oc:share-types/> <oc:share-types/>
</d:prop> </d:prop>
</d:propfind>"#, </d:propfind>"#);
); let response_text = client.request(method, url)
let response_text = client .basic_auth("adam", Some(password))
.request(method, url)
.basic_auth(config.credentials.username.as_str(), config.credentials.password.as_ref())
.body(body) .body(body)
.send() .send()
.await? .await?
.text() .text()
.await?; .await?;
debug!("[get_folder_contents] response_text: {:?}", response_text); debug!("{:?}", response_text);
Ok(response_text) Ok(response_text)
} }
async fn publicise_it(path: &str, config: &Config) -> Result<bool, Box<dyn std::error::Error>> { fn get_password() -> Result<String, std::io::Error> {
debug!("[publicise_it] Entering function..."); print!("Nextcloud password: ");
let base_url = Url::parse(config.paths.root.as_str())?; io::stdout().flush().unwrap();
let url = base_url.join("ocs/v2.php/apps/files_sharing/api/v1/shares")?; let mut buffer = String::new();
let method = reqwest::Method::POST; let stdin = io::stdin();
let client = reqwest::Client::new(); match stdin.read_line(&mut buffer) {
let params = [("path", path), ("shareType", "3")]; Ok(_) => Ok(buffer),
debug!("[publicise_it] url: {}", url); Err(error) => Err(error),
debug!("[publicise_it] params: {:?}", params); }
let response_text = client
.request(method, url)
.basic_auth(config.credentials.username.as_str(), config.credentials.password.as_ref())
.header("OCS-APIRequest", "true")
.form(&params)
.send()
.await?
.text()
.await?;
debug!("[publicise_it] response_text: {:?}", response_text);
Ok(true)
} }
async fn traverse(mut result: Multistatus, config: &Config) -> Result<bool, Box<dyn std::error::Error>> { fn indent(size: usize) -> String {
debug!("[traverse] Entering function..."); const INDENT: &'static str = " ";
let mut current_index: usize = 0; (0..size).map(|_| INDENT)
let mut visited_items = HashMap::new(); .fold(String::with_capacity(size*INDENT.len()), |r, s| r + s)
let href_key = (&mut result.response[current_index].href).clone(); }
visited_items.insert(href_key, true);
current_index += 1;
while current_index < (&mut result.response).len() { fn parse_xml(xml_string: String) {
debug!("[traverse] current_index: {:?}", current_index); let xml_str = &xml_string;
debug!("[traverse] current href: {}", &result.response[current_index].href); let parser = EventReader::from_str(xml_str);
let href_key = (&mut result.response[current_index].href).clone(); let mut depth = 0;
if !visited_items.contains_key(&href_key) { let mut resourcetype_element = false;
debug!("[traverse] Fresh item..."); let mut share_type_element = false;
if !(&mut result.response[current_index].propstat[0].prop.resource_type.collection).is_empty() { for e in parser {
debug!("[traverse] Collection..."); match e {
let folder_contents: String = get_folder_contents( Ok(XmlEvent::StartElement { name, .. }) => {
&result.response[current_index].href, println!("{}+{}", indent(depth), name);
config, depth += 1;
) if name.local_name.matches("resourcetype").collect::<Vec<&str>>().len() > 0 {
.await println!("{} Element: resourcetype", indent(depth));
.unwrap(); resourcetype_element = true;
let mut new_result: Multistatus = from_str(&String::from(folder_contents)).unwrap();
debug!("[traverse] Parsed:\n{:?}", new_result);
result.response.append(&mut new_result.response);
debug!("[traverse] new vector length: {}", &result.response.len());
} else {
debug!("[traverse] Node...");
if !(&mut result.response[current_index].propstat[0].prop.share_types).contains(&ShareType{ share_type: 3 }) {
debug!("[traverse] it's not public");
let username = config.credentials.username.as_str();
let username_seg_string = format!("/{}/", username);
let username_seg = username_seg_string.as_str();
let index = &result.response[current_index].href.find(username_seg).unwrap_or(0);
let new_index = index + username.len() + 2;
let new_href = &result.response[current_index].href[new_index..];
publicise_it(new_href, config).await.unwrap();
} else {
debug!("[traverse] it's already public");
} }
} }
visited_items.insert(href_key, true); Ok(XmlEvent::Characters(s)) => {
debug!("[traverse] visited items: {:?}", visited_items); println!("{} {}", indent(depth), s);
} else { }
debug!("[traverse] Already-visited item."); Ok(XmlEvent::EndElement { name }) => {
depth -= 1;
println!("{}-{}", indent(depth), name);
}
Err(e) => {
println!("Error: {}", e);
break;
}
_ => {}
} }
current_index += 1;
} }
Ok(true)
} }
#[tokio::main] #[tokio::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
env_logger::Builder::from_env(Env::default()).init(); env_logger::Builder::from_env(Env::default().default_filter_or("trace")).target(Target::Stdout).init();
println!("Publicise it!\n\n"); println!("Publicise it!");
let password = get_password().unwrap().trim().to_string();
debug!("Received password: {}[END]", password);
let mut config = init().unwrap(); let folder_contents = get_folder_contents(password, "/nextcloud/remote.php/dav/files/adam/test_public").await.unwrap();
if config.credentials.password == None { debug!("{:?}", folder_contents);
let output = Command::new(&config.credentials.password_script.as_ref().unwrap()).output().unwrap();
if output.stdout.len() == 0 {
panic!("[main] Failed to acquire password from provided script.");
}
config.credentials.password = Some(String::from_utf8(output.stdout).unwrap());
}
debug!("[main] {:?}", &config);
let full_path = &(String::from("/remote.php/dav/files/") + &config.credentials.username + "/" + &config.paths.target); parse_xml(folder_contents);
let folder_contents: String =
get_folder_contents(full_path, &config)
.await
.unwrap();
let result: Multistatus = from_str(&folder_contents).unwrap();
debug!("[main] {:?}", result);
let _ = traverse(result, &config).await.unwrap();
Ok(()) Ok(())
} }
// TO_BE_DELETED: Testing removal of SSH password.