-
Notifications
You must be signed in to change notification settings - Fork 6
provide shortened url if a shortener is used on the host and --shorte… #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,14 @@ | ||
| use clap::Parser; | ||
| use data_url::DataUrl; | ||
| use url::Url; | ||
| use reqwest::blocking::Client; | ||
| use pbcli::api::API; | ||
| use pbcli::error::{PasteError, PbResult}; | ||
| use pbcli::opts::Opts; | ||
| use pbcli::privatebin::{DecryptedComment, DecryptedCommentsMap, DecryptedPaste}; | ||
| use pbcli::util::check_filesize; | ||
| use serde_json::Value; | ||
| use std::time::Duration; | ||
| use std::ffi::OsString; | ||
| use std::io::IsTerminal; | ||
| use std::io::{Read, Write}; | ||
|
|
@@ -99,6 +102,86 @@ fn handle_get(opts: &Opts) -> PbResult<()> { | |
| Ok(()) | ||
| } | ||
|
|
||
| fn shorten_via_privatebin(opts: &Opts, long_url: &str) -> PbResult<String> { | ||
| fn try_method(opts: &Opts, long_url: &str, method: &str) -> PbResult<String> { | ||
| let encoded = url::form_urlencoded::byte_serialize(long_url.as_bytes()).collect::<String>(); | ||
|
|
||
| // Always shorten on the same host as --host | ||
| let mut endpoint = opts.get_url().clone(); | ||
| endpoint.set_fragment(None); | ||
| endpoint.set_path("/"); | ||
| endpoint.set_query(Some(&format!("{method}&link={encoded}"))); | ||
|
|
||
| let client = Client::builder().timeout(Duration::from_secs(5)).build()?; | ||
| let resp_text = client | ||
| .get(endpoint.clone()) | ||
| .send()? | ||
| .error_for_status()? | ||
| .text()?; | ||
|
|
||
| let text = resp_text.trim(); | ||
|
|
||
| log::debug!("shortener ({}) GET: {}", method, endpoint); | ||
| log::debug!("shortener ({}) raw response: {}", method, text); | ||
|
|
||
| // JSON first (some proxies return JSON) | ||
| if let Ok(v) = serde_json::from_str::<Value>(text) { | ||
| if let Some(s) = v.get("shorturl").and_then(|x| x.as_str()) { | ||
| return Ok(s.to_string()); | ||
| } | ||
| if let Some(s) = v.get("url").and_then(|x| x.as_str()) { | ||
| return Ok(s.to_string()); | ||
| } | ||
| } | ||
|
|
||
| // HTML: accept only <a id="pasteurl" href="..."> | ||
| if let Some(id_pos) = text.find("id=\"pasteurl\"") { | ||
| let after_id = &text[id_pos..]; | ||
|
|
||
| if let Some(href_pos) = after_id.find("href=\"") { | ||
| let after_href = &after_id[href_pos + "href=\"".len()..]; | ||
| if let Some(end) = after_href.find('"') { | ||
| let url = &after_href[..end]; | ||
| if url.starts_with("https://") || url.starts_with("http://") { | ||
| return Ok(url.to_string()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if let Some(href_pos) = after_id.find("href='") { | ||
| let after_href = &after_id[href_pos + "href='".len()..]; | ||
| if let Some(end) = after_href.find('\'') { | ||
| let url = &after_href[..end]; | ||
| if url.starts_with("https://") || url.starts_with("http://") { | ||
| return Ok(url.to_string()); | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+141
to
+159
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as below should be possible here to reduce code duplication, since I believe the only difference is ' or ": Turn those into an array and use find_map (like below) to get the first non-None result |
||
| } | ||
|
|
||
| Err(PasteError::InvalidData) | ||
| } | ||
|
|
||
| // 1) try YOURLS proxy first | ||
| match try_method(opts, long_url, "shortenviayourls") { | ||
| Ok(u) => Ok(u), | ||
| Err(e1) => { | ||
| log::debug!("YOURLS shorten failed ({e1:?}), trying shlink…"); | ||
|
|
||
| // 2) fall back to shlink proxy | ||
| match try_method(opts, long_url, "shortenviashlink") { | ||
| Ok(u) => Ok(u), | ||
| Err(e2) => { | ||
| // preserve useful debug, but return a single error | ||
| log::debug!("Shlink shorten also failed ({e2:?})"); | ||
| Err(PasteError::InvalidData) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+166
to
+181
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of a nested retry, I would propose a different construct: turn the methods into an array and loop over it, aborting on the first non-None result, this can be done with |
||
| } | ||
|
|
||
|
|
||
| fn handle_post(opts: &Opts) -> PbResult<()> { | ||
| let url = opts.get_url(); | ||
| let stdin = get_stdin()?; | ||
|
|
@@ -135,14 +218,39 @@ fn handle_post(opts: &Opts) -> PbResult<()> { | |
| } | ||
|
|
||
| let res = api.post_paste(&paste, password, opts)?; | ||
| let long_url = res.to_paste_url().to_string(); | ||
|
|
||
| let should_shorten = opts.shorten && !opts.no_shorten; | ||
|
|
||
| let short_url = if should_shorten { | ||
| match shorten_via_privatebin(opts, &long_url) { | ||
| Ok(s) => Some(s), | ||
| Err(e) => { | ||
| log::debug!("shorten failed, falling back to long URL: {e:?}"); | ||
|
|
||
| // User-facing notice (stderr so stdout remains just the URL) | ||
| eprintln!( | ||
| "--shorten specified but no URL shortener was found configured on host, falling back to host-provided URL." | ||
| ); | ||
|
|
||
| None | ||
| } | ||
| } | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| if opts.json { | ||
| let mut output: Value = serde_json::to_value(res.clone())?; | ||
| output["pasteurl"] = Value::String(res.to_paste_url().to_string()); | ||
| output["pasteurl"] = Value::String(long_url.clone()); | ||
| output["deleteurl"] = Value::String(res.to_delete_url().to_string()); | ||
| if let Some(s) = &short_url { | ||
| output["shorturl"] = Value::String(s.clone()); | ||
| } | ||
| std::io::stdout().write_all(serde_json::to_string_pretty(&output)?.as_bytes())?; | ||
| } else { | ||
| std::io::stdout().write_all(res.to_paste_url().as_str().as_bytes())?; | ||
| let to_print = short_url.as_deref().unwrap_or(&long_url); | ||
| std::io::stdout().write_all(to_print.as_bytes())?; | ||
| writeln!(std::io::stdout())?; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is unused, isn't it? If yes we can remove it