Fallback to extension-based content type detection when parsing Content-Type header fails (#13610)

# Description

Previously when nushell failed to parse the content type header, it
would emit an error instead of returning the response. Now it will fall
back to `text/plain` (which, in turn, will trigger type detection based
on file extension).

May fix (potentially) nushell/nushell#11927

Refs:
https://discord.com/channels/601130461678272522/614593951969574961/1272895236489613366

Supercedes: #13609 

# User-Facing Changes

It's now possible to fetch content even if the server returns an invalid
content type header. Users may need to parse the response manually, but
it's still better than not getting the response at all.

# Tests + Formatting

Added a test for the new behaviour.

# After Submitting
This commit is contained in:
Bruce Weirdan 2024-08-14 16:47:01 +02:00 committed by GitHub
parent e841fce0f9
commit e690e7aac0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 9 deletions

View File

@ -4,7 +4,6 @@ use base64::{
engine::{general_purpose::PAD, GeneralPurpose},
Engine,
};
use fancy_regex::Regex;
use multipart_rs::MultipartWriter;
use nu_engine::command_prelude::*;
use nu_protocol::{ByteStream, LabeledError, Signals};
@ -561,14 +560,12 @@ fn transform_response_using_content_type(
resp: Response,
content_type: &str,
) -> Result<PipelineData, ShellError> {
let regex = Regex::new("\"").expect("Failed to create regex");
let content_type_trim = regex.replace_all(content_type, "").to_string();
let content_type = mime::Mime::from_str(&content_type_trim).map_err(|err| {
LabeledError::new(err.to_string())
.with_help("given unknown MIME type, or error parsing MIME type")
.with_label(format!("MIME type unknown: {content_type_trim}"), span)
})?;
let content_type = mime::Mime::from_str(content_type)
// there are invalid content types in the wild, so we try to recover
// Example: `Content-Type: "text/plain"; charset="utf8"` (note the quotes)
.or_else(|_| mime::Mime::from_str(&content_type.replace('"', "")))
.or_else(|_| mime::Mime::from_str("text/plain"))
.expect("Failed to parse content type, and failed to default to text/plain");
let ext = match (content_type.type_(), content_type.subtype()) {
(mime::TEXT, mime::PLAIN) => {

View File

@ -273,3 +273,46 @@ fn http_get_self_signed_override() {
let actual = nu!("http get --insecure https://self-signed.badssl.com/");
assert!(actual.out.contains("<html>"));
}
#[test]
fn http_get_with_invalid_mime_type() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/foo.nuon")
.with_status(200)
// `what&ever` is not a parseable MIME type
.with_header("content-type", "what&ever")
.with_body("[1 2 3]")
.create();
// but `from nuon` is a known command in nu, so we take `foo.{ext}` and pass it to `from {ext}`
let actual = nu!(pipeline(
format!(
r#"http get {url}/foo.nuon | to json --raw"#,
url = server.url()
)
.as_str()
));
assert_eq!(actual.out, "[1,2,3]");
}
#[test]
fn http_get_with_unknown_mime_type() {
let mut server = Server::new();
let _mock = server
.mock("GET", "/foo")
.with_status(200)
// `application/nuon` is not an IANA-registered MIME type
.with_header("content-type", "application/nuon")
.with_body("[1 2 3]")
.create();
// but `from nuon` is a known command in nu, so we take `{garbage}/{whatever}` and pass it to `from {whatever}`
let actual = nu!(pipeline(
format!(r#"http get {url}/foo | to json --raw"#, url = server.url()).as_str()
));
assert_eq!(actual.out, "[1,2,3]");
}