Skip to content

Commit cb1f988

Browse files
authored
fix(executor): add Content-Length: 0 for body-less POST requests (npm#183)
* fix(executor): add Content-Length: 0 header for body-less POST/PUT/PATCH requests Google API servers return HTTP 411 (Length Required) when a POST request is sent without a Content-Length header, even if there is no body. This affects all Discovery API methods where httpMethod is POST but no requestBody is defined (e.g. gmail users.messages.trash). Fixes npm#182 * chore: add changeset for content-length fix
1 parent 88cb65c commit cb1f988

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@googleworkspace/cli": patch
3+
---
4+
5+
Add Content-Length: 0 header for POST/PUT/PATCH requests with no body to fix HTTP 411 errors

src/executor.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ async fn build_http_request(
191191
} else if let Some(ref body_val) = input.body {
192192
request = request.header("Content-Type", "application/json");
193193
request = request.json(body_val);
194+
} else if matches!(method.http_method.as_str(), "POST" | "PUT" | "PATCH") {
195+
request = request.header("Content-Length", "0");
194196
}
195197
}
196198

@@ -1817,3 +1819,84 @@ fn test_get_value_type_helper() {
18171819
assert_eq!(get_value_type(&json!([1, 2])), "array");
18181820
assert_eq!(get_value_type(&json!({"a": 1})), "object");
18191821
}
1822+
1823+
#[tokio::test]
1824+
async fn test_post_without_body_sets_content_length_zero() {
1825+
let client = reqwest::Client::new();
1826+
let method = RestMethod {
1827+
http_method: "POST".to_string(),
1828+
path: "messages/trash".to_string(),
1829+
..Default::default()
1830+
};
1831+
let input = ExecutionInput {
1832+
full_url: "https://example.com/messages/trash".to_string(),
1833+
body: None,
1834+
params: Map::new(),
1835+
query_params: HashMap::new(),
1836+
is_upload: false,
1837+
};
1838+
1839+
let request = build_http_request(&client, &method, &input, None, &AuthMethod::None, None, 0, None)
1840+
.await
1841+
.unwrap();
1842+
1843+
let built = request.build().unwrap();
1844+
assert_eq!(
1845+
built.headers().get("Content-Length").map(|v| v.to_str().unwrap()),
1846+
Some("0"),
1847+
"POST with no body must include Content-Length: 0"
1848+
);
1849+
}
1850+
1851+
#[tokio::test]
1852+
async fn test_post_with_body_does_not_add_content_length_zero() {
1853+
let client = reqwest::Client::new();
1854+
let method = RestMethod {
1855+
http_method: "POST".to_string(),
1856+
path: "files".to_string(),
1857+
..Default::default()
1858+
};
1859+
let input = ExecutionInput {
1860+
full_url: "https://example.com/files".to_string(),
1861+
body: Some(json!({"name": "test"})),
1862+
params: Map::new(),
1863+
query_params: HashMap::new(),
1864+
is_upload: false,
1865+
};
1866+
1867+
let request = build_http_request(&client, &method, &input, None, &AuthMethod::None, None, 0, None)
1868+
.await
1869+
.unwrap();
1870+
1871+
let built = request.build().unwrap();
1872+
// When body is present, Content-Length should NOT be "0"
1873+
let cl = built.headers().get("Content-Length").map(|v| v.to_str().unwrap().to_string());
1874+
assert!(cl.is_none() || cl.as_deref() != Some("0"));
1875+
}
1876+
1877+
#[tokio::test]
1878+
async fn test_get_does_not_set_content_length_zero() {
1879+
let client = reqwest::Client::new();
1880+
let method = RestMethod {
1881+
http_method: "GET".to_string(),
1882+
path: "files".to_string(),
1883+
..Default::default()
1884+
};
1885+
let input = ExecutionInput {
1886+
full_url: "https://example.com/files".to_string(),
1887+
body: None,
1888+
params: Map::new(),
1889+
query_params: HashMap::new(),
1890+
is_upload: false,
1891+
};
1892+
1893+
let request = build_http_request(&client, &method, &input, None, &AuthMethod::None, None, 0, None)
1894+
.await
1895+
.unwrap();
1896+
1897+
let built = request.build().unwrap();
1898+
assert!(
1899+
built.headers().get("Content-Length").is_none(),
1900+
"GET with no body should not have Content-Length header"
1901+
);
1902+
}

0 commit comments

Comments
 (0)