Follow URL redirects before downloading, this passes URL shorteners

This commit is contained in:
timvisee 2019-03-15 00:54:56 +01:00
parent e2ebbc91a9
commit dd3a4c561f
No known key found for this signature in database
GPG key ID: B8DB720BC383E172
6 changed files with 70 additions and 28 deletions

View file

@ -19,13 +19,14 @@ use tempfile::{Builder as TempBuilder, NamedTempFile};
use super::select_api_version;
#[cfg(feature = "archive")]
use crate::archive::archive::Archive;
use crate::client::create_transfer_client;
use crate::client::{create_client, create_transfer_client};
use crate::cmd::matcher::{download::DownloadMatcher, main::MainMatcher, Matcher};
#[cfg(feature = "history")]
use crate::history_tool;
use crate::progress::ProgressBar;
use crate::util::{
ensure_enough_space, ensure_password, prompt_yes, quit, quit_error, quit_error_msg, ErrorHints,
ensure_enough_space, ensure_password, follow_url, print_error, prompt_yes, quit, quit_error,
quit_error_msg, ErrorHints,
};
/// A file download action.
@ -46,12 +47,21 @@ impl<'a> Download<'a> {
let matcher_main = MainMatcher::with(self.cmd_matches).unwrap();
let matcher_download = DownloadMatcher::with(self.cmd_matches).unwrap();
// Get the share URL, derive the host
let url = matcher_download.url();
let host = matcher_download.guess_host();
// Create a regular client
let client = create_client(&matcher_main);
// Create a reqwest client capable for downloading files
let client = create_transfer_client(&matcher_main);
// Get the share URL, attempt to follow it
let url = matcher_download.url();
let url = match follow_url(&client, &url) {
Ok(url) => url,
Err(err) => {
print_error(err.context("failed to follow share URL, ignoring").compat());
url
}
};
// Guess the host
let host = matcher_download.guess_host(Some(url.clone()));
// Determine the API version to use
let mut desired_version = matcher_main.api();
@ -154,6 +164,9 @@ impl<'a> Download<'a> {
let progress_bar = Arc::new(Mutex::new(ProgressBar::new_download()));
let progress_reader: Arc<Mutex<ProgressReporter>> = progress_bar;
// Create a transfer client
let transfer_client = create_transfer_client(&matcher_main);
// Execute an download action
let progress = if !matcher_main.quiet() {
Some(progress_reader)
@ -161,7 +174,7 @@ impl<'a> Download<'a> {
None
};
ApiDownload::new(api_version, &file, target, password, false, Some(metadata))
.invoke(&client, progress)?;
.invoke(&transfer_client, progress)?;
// Extract the downloaded file if working with an archive
#[cfg(feature = "archive")]

View file

@ -321,10 +321,7 @@ impl<'a> Upload<'a> {
{
if matcher_upload.qrcode() {
if let Err(err) = print_qr(url.as_str()) {
print_error(
err.context("failed to print QR code, ignoring")
.compat(),
);
print_error(err.context("failed to print QR code, ignoring").compat());
}
}
}

View file

@ -1,8 +1,4 @@
use chbs::{
config::BasicConfig,
prelude::*,
word::WordList,
};
use chbs::{config::BasicConfig, prelude::*, word::WordList};
use clap::Arg;
use super::{CmdArg, CmdArgFlag};

View file

@ -26,8 +26,8 @@ impl<'a: 'b, 'b> DownloadMatcher<'a> {
/// Guess the file share host, based on the file share URL.
///
/// See `Self::url`.
pub fn guess_host(&'a self) -> Url {
let mut url = self.url();
pub fn guess_host(&'a self, url: Option<Url>) -> Url {
let mut url = url.unwrap_or(self.url());
url.set_path("");
url.set_query(None);
url.set_fragment(None);

View file

@ -2,11 +2,11 @@
use ffsend_api::{
api::request::{ensure_success, ResponseError},
url::{self, Url},
reqwest::{self, Client},
url::{self, Url},
};
use urlshortener::{
providers::{Provider, self},
providers::{self, Provider},
request::{Method, Request},
};
@ -21,9 +21,7 @@ pub fn shorten(client: &Client, url: &str) -> Result<String> {
/// Shorten the given URL.
pub fn shorten_url(client: &Client, url: &Url) -> Result<Url> {
Url::parse(
&shorten(client, url.as_str())?,
).map_err(|err| err.into())
Url::parse(&shorten(client, url.as_str())?).map_err(|err| err.into())
}
/// Do the request as given, return the response.
@ -59,8 +57,7 @@ fn request(client: &Client, req: Request) -> Result<String> {
}
// Send the request, ensure success
let mut response = builder.send()
.map_err(Error::Request)?;
let mut response = builder.send().map_err(Error::Request)?;
ensure_success(&response)?;
// Respond with the body text
@ -84,7 +81,7 @@ pub enum Error {
/// An error occurred while parsing the shortened URL.
#[fail(display = "failed to shorten URL, could not parse URL")]
Url(#[cause] url::ParseError)
Url(#[cause] url::ParseError),
}
impl From<url::ParseError> for Error {

View file

@ -29,7 +29,11 @@ use chrono::Duration;
use failure::{err_msg, Fail};
#[cfg(all(feature = "clipboard", not(target_os = "linux")))]
use failure::{Compat, Error};
use ffsend_api::url::Url;
use ffsend_api::{
api::request::{ensure_success, ResponseError},
reqwest::{self, Client},
url::Url,
};
use rpassword::prompt_password_stderr;
use crate::cmd::matcher::MainMatcher;
@ -857,3 +861,38 @@ pub fn api_version_list() -> Vec<&'static str> {
versions
}
/// Follow redirects on the given URL, and return the final full URL.
///
/// This is used to obtain share URLs from shortened links.
///
// TODO: extract this into module
pub fn follow_url(client: &Client, url: &Url) -> Result<Url, FollowError> {
// Send the request, follow the URL, ensure success
let response = client
.get(url.as_str())
.send()
.map_err(FollowError::Request)?;
ensure_success(&response)?;
// Obtain the final URL
Ok(response.url().clone())
}
/// URL following error.
#[derive(Debug, Fail)]
pub enum FollowError {
/// Failed to send the shortening request.
#[fail(display = "failed to send URL follow request")]
Request(#[cause] reqwest::Error),
/// The server responded with a bad response.
#[fail(display = "failed to shorten URL, got bad response")]
Response(#[cause] ResponseError),
}
impl From<ResponseError> for FollowError {
fn from(err: ResponseError) -> Self {
FollowError::Response(err)
}
}