Skip to content

Commit

Permalink
Preserve non-standard/unparseable query strings as-is in snippet output
Browse files Browse the repository at this point in the history
This is particularly notable for cases like the &&& repro here, but is
also clearly visible in cases like ?a=b&c, which would often become
something more like '?a=b&c='.

This only applies when the queryString in the HAR is empty, which will
be rare, but HTTP Toolkit (and others) can use this to leave that blank
and send only the raw URL to preserve formatting at the cost of the
losing the nice structure query param setting code in the output.
  • Loading branch information
pimterry committed Jul 19, 2024
1 parent bf61e2e commit d21b225
Show file tree
Hide file tree
Showing 37 changed files with 348 additions and 14 deletions.
37 changes: 23 additions & 14 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,20 +219,29 @@ HTTPSnippet.prototype.prepare = function (request) {
// merge all possible queryString values
request.queryObj = Object.assign(request.queryObj, request.uriObj.query)

// reset uriObj values for a clean url
request.uriObj.query = null
request.uriObj.search = null
request.uriObj.path = request.uriObj.pathname

// keep the base url clean of queryString
request.url = url.format(request.uriObj)

// update the uri object
request.uriObj.query = request.queryObj
request.uriObj.search = qs.stringify(request.queryObj)

if (request.uriObj.search) {
request.uriObj.path = request.uriObj.pathname + '?' + request.uriObj.search
// In some cases (unparseable query - preference to use raw in exporter) the queryString might
// be empty while the URL search is not. In that case, we prefer the URL search.
const hasQueryObj = Object.keys(request.queryObj).length > 0
if (hasQueryObj || !request.uriObj.search) {
// reset uriObj values for a clean url
request.uriObj.query = null
request.uriObj.search = null
request.uriObj.path = request.uriObj.pathname

// keep the base url clean of queryString
request.url = url.format(request.uriObj)

// update the uri object
request.uriObj.query = request.queryObj
request.uriObj.search = qs.stringify(request.queryObj)

if (request.uriObj.search) {
request.uriObj.path = request.uriObj.pathname + '?' + request.uriObj.search
}
} else {
// We have no request.queryObj (so snippets won't send query params in a structured way)
// We keep the queryString in request.url (so it's sent raw everywhere)
// request.fullUrl is recreated below (maybe mild fixed?) but preserves raw search etc
}

// construct a full url
Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/output/c/libcurl/unparseable-query.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CURL *hnd = curl_easy_init();

curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(hnd, CURLOPT_URL, "http://mockbin.com/har?&&&");

CURLcode ret = curl_easy_perform(hnd);
3 changes: 3 additions & 0 deletions test/fixtures/output/clojure/clj_http/unparseable-query.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(require '[clj-http.client :as client])

(client/get "http://mockbin.com/har?&&&")
12 changes: 12 additions & 0 deletions test/fixtures/output/csharp/httpclient/unparseable-query.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
var client = new HttpClient();
var request = new HttpRequestMessage
{
Method = HttpMethod.Get,
RequestUri = new Uri("http://mockbin.com/har?&&&"),
};
using (var response = await client.SendAsync(request))
{
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body);
}
3 changes: 3 additions & 0 deletions test/fixtures/output/csharp/restsharp/unparseable-query.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var client = new RestClient("http://mockbin.com/har?&&&");
var request = new RestRequest(Method.GET);
IRestResponse response = client.Execute(request);
23 changes: 23 additions & 0 deletions test/fixtures/output/go/native/unparseable-query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"fmt"
"net/http"
"io"
)

func main() {

url := "http://mockbin.com/har?&&&"

req, _ := http.NewRequest("GET", url, nil)

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(res)
fmt.Println(string(body))

}
2 changes: 2 additions & 0 deletions test/fixtures/output/http/1.1/unparseable-query
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GET /har?&&& HTTP/1.1
Host: mockbin.com
8 changes: 8 additions & 0 deletions test/fixtures/output/java/asynchttp/unparseable-query.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
AsyncHttpClient client = new DefaultAsyncHttpClient();
client.prepare("GET", "http://mockbin.com/har?&&&")
.execute()
.toCompletableFuture()
.thenAccept(System.out::println)
.join();

client.close();
6 changes: 6 additions & 0 deletions test/fixtures/output/java/nethttp/unparseable-query.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://mockbin.com/har?&&&"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
8 changes: 8 additions & 0 deletions test/fixtures/output/java/okhttp/unparseable-query.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
.url("http://mockbin.com/har?&&&")
.get()
.build();

Response response = client.newCall(request).execute();
2 changes: 2 additions & 0 deletions test/fixtures/output/java/unirest/unparseable-query.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
HttpResponse<String> response = Unirest.get("http://mockbin.com/har?&&&")
.asString();
9 changes: 9 additions & 0 deletions test/fixtures/output/javascript/axios/unparseable-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import axios from "axios";

const options = {method: 'GET', url: 'http://mockbin.com/har?&&&'};

axios.request(options).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
6 changes: 6 additions & 0 deletions test/fixtures/output/javascript/fetch/unparseable-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const options = {method: 'GET'};

fetch('http://mockbin.com/har?&&&', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
11 changes: 11 additions & 0 deletions test/fixtures/output/javascript/jquery/unparseable-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const settings = {
"async": true,
"crossDomain": true,
"url": "http://mockbin.com/har?&&&",
"method": "GET",
"headers": {}
};

$.ajax(settings).done(function (response) {
console.log(response);
});
14 changes: 14 additions & 0 deletions test/fixtures/output/javascript/xhr/unparseable-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const data = null;

const xhr = new XMLHttpRequest();
xhr.withCredentials = true;

xhr.addEventListener("readystatechange", function () {
if (this.readyState === this.DONE) {
console.log(this.responseText);
}
});

xhr.open("GET", "http://mockbin.com/har?&&&");

xhr.send(data);
8 changes: 8 additions & 0 deletions test/fixtures/output/kotlin/okhttp/unparseable-query.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
val client = OkHttpClient()

val request = Request.Builder()
.url("http://mockbin.com/har?&&&")
.get()
.build()

val response = client.newCall(request).execute()
9 changes: 9 additions & 0 deletions test/fixtures/output/node/axios/unparseable-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
var axios = require("axios").default;

var options = {method: 'GET', url: 'http://mockbin.com/har?&&&'};

axios.request(options).then(function (response) {
console.log(response.data);
}).catch(function (error) {
console.error(error);
});
10 changes: 10 additions & 0 deletions test/fixtures/output/node/fetch/unparseable-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const fetch = require('node-fetch');

let url = 'http://mockbin.com/har?&&&';

let options = {method: 'GET'};

fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error('error:' + err));
24 changes: 24 additions & 0 deletions test/fixtures/output/node/native/unparseable-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const http = require("http");

const options = {
"method": "GET",
"hostname": "mockbin.com",
"port": null,
"path": "/har?&&&",
"headers": {}
};

const req = http.request(options, function (res) {
const chunks = [];

res.on("data", function (chunk) {
chunks.push(chunk);
});

res.on("end", function () {
const body = Buffer.concat(chunks);
console.log(body.toString());
});
});

req.end();
9 changes: 9 additions & 0 deletions test/fixtures/output/node/request/unparseable-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const request = require('request');

const options = {method: 'GET', url: 'http://mockbin.com/har?&&&'};

request(options, function (error, response, body) {
if (error) throw new Error(error);

console.log(body);
});
9 changes: 9 additions & 0 deletions test/fixtures/output/node/unirest/unparseable-query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const unirest = require("unirest");

const req = unirest("GET", "http://mockbin.com/har?&&&");

req.end(function (res) {
if (res.error) throw new Error(res.error);

console.log(res.body);
});
18 changes: 18 additions & 0 deletions test/fixtures/output/objc/nsurlsession/unparseable-query.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#import <Foundation/Foundation.h>

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://mockbin.com/har?&&&"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:@"GET"];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"%@", error);
} else {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSLog(@"%@", httpResponse);
}
}];
[dataTask resume];
9 changes: 9 additions & 0 deletions test/fixtures/output/ocaml/cohttp/unparseable-query.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
open Cohttp_lwt_unix
open Cohttp
open Lwt

let uri = Uri.of_string "http://mockbin.com/har?&&&" in

Client.call `GET uri
>>= fun (res, body_stream) ->
(* Do stuff with the result *)
24 changes: 24 additions & 0 deletions test/fixtures/output/php/curl/unparseable-query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => 'http://mockbin.com/har?&&&',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo 'cURL Error #:' . $err;
} else {
echo $response;
}
13 changes: 13 additions & 0 deletions test/fixtures/output/php/http1/unparseable-query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

$request = new HttpRequest();
$request->setUrl('http://mockbin.com/har?&&&');
$request->setMethod(HTTP_METH_GET);

try {
$response = $request->send();

echo $response->getBody();
} catch (HttpException $ex) {
echo $ex;
}
11 changes: 11 additions & 0 deletions test/fixtures/output/php/http2/unparseable-query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

$client = new http\Client;
$request = new http\Client\Request;

$request->setRequestUrl('http://mockbin.com/har?&&&');
$request->setRequestMethod('GET');
$client->enqueue($request)->send();
$response = $client->getResponse();

echo $response->getBody();
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$response = Invoke-RestMethod -Uri 'http://mockbin.com/har?&&&' -Method GET
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$response = Invoke-WebRequest -Uri 'http://mockbin.com/har?&&&' -Method GET
10 changes: 10 additions & 0 deletions test/fixtures/output/python/python3/unparseable-query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import http.client

conn = http.client.HTTPConnection("mockbin.com")

conn.request("GET", "/har?&&&")

res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
7 changes: 7 additions & 0 deletions test/fixtures/output/python/requests/unparseable-query.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import requests

url = "http://mockbin.com/har?&&&"

response = requests.get(url)

print(response.text)
7 changes: 7 additions & 0 deletions test/fixtures/output/r/httr/unparseable-query.r
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
library(httr)

url <- "http://mockbin.com/har?&&&"

response <- VERB("GET", url, content_type("application/octet-stream"))

content(response, "text")
11 changes: 11 additions & 0 deletions test/fixtures/output/ruby/native/unparseable-query.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'uri'
require 'net/http'

url = URI("http://mockbin.com/har?&&&")

http = Net::HTTP.new(url.host, url.port)

request = Net::HTTP::Get.new(url)

response = http.request(request)
puts response.read_body
Loading

0 comments on commit d21b225

Please sign in to comment.