# Mutilating the Query

I know you've always thought about the trials and tribulations of experiencing the joys of life as a web proxy. Honestly, who hasn't? Today you're tasked with realizing this goal (at least a part of it). Website X gets a lot of traffic on the daily and is looking for a PaaS (clearly this refers to Proxy as a Service) because of the large number of users who insist on passing sensitive information via query parameters (users are silly). Your task is to remove any and all sensitive query parameters from the request before forwarding the request through to its original destination.

## Input

• A well-formed absolute HTTP URL that follows the URI grammar in RFC3986 Section 3.
• You can assume there is no fragment
• Brief format example where anything in square brackets denotes optional: http[s]://[user:pass@]host.name.com[:port]/[?param1=value1&param2=value2...]
• A list of query parameters to be removed.

## Output

The modified HTTP URL without the parameters defined in the input list.

## Examples

http://example.com/ [foo]
> http://example.com/

http://example.com/?foo=bar []
> http://example.com/?foo=bar

http://example.com/ []
> http://example.com/

http://example.com/?foo=1&bar=2&baz=3 [foo,baz]
> http://example.com/?bar=2

http://example.com/?foo=1&bar=2&baz=3 [foo,bar,baz]
> http://example.com/

http://example.com/?foo&bar=2&baz= [foo,baz]
> http://example.com/?bar=2

http://example.com/?abc=1&def=2&baz=foo [foo,bar]
> http://example.com/?abc=1&def=2&baz=foo

http://example.com/?foobar=baz [foo]
> http://example.com/?foobar=baz

http://foo:foo@foo.com:8080/?foo=1&bar=foo [foo]
> http://foo:foo@foo.com:8080/?bar=foo


## Scoring

This is , so the shortest answer (in bytes) wins.

• Can I get the URL and the query parameters each on separate lines? Sep 19, 2016 at 17:59
• Can & appear anywhere other than between parameters? Sep 19, 2016 at 18:00
• also can e.g. the password contain a ?? Also should the order be kept like it was? Sep 19, 2016 at 18:30
• @Riley No. If & is part of a query parameter it should be correctly urlencoded as %26
– Poke
Sep 19, 2016 at 18:44
• Apparently, http://foo:&foo=x@foo.com:8080/?foo=1&bar=foo is allowed by the RFC. This should break a bunch of the existing solutions. :D (The rule is userinfo can be expanded as unreserved or pct-escape or sub-delims, and sub-delims can have & and =) Sep 20, 2016 at 9:03

# GNU sed 98 96 88 80 77 74 69 59 54 (48 + 1 for -r) 49

:;s,(.+)(=[^&]*[& ]|&)(.*)\1,\3 ,
t;s,[?&]? .*,,


The list of parameters to remove are separated by spaces.

$echo 'http://example.com/?foo=1&bar=2&baz=3 foo bar baz' | sed -rf sed.txt http://example.com/$ echo 'http://example.com/?foo&bar=2&baz= foo baz' | sed -rf sed.txt
http://example.com/?bar=2

$echo 'http://example.com/' | sed -rf sed.txt http://example.com/  • In your current code edit, multiple tests from OP's question give a trailing & or ? character in the resulting URL. Sep 19, 2016 at 19:52 • @seshoumara I'm not sure how I missed that... Luckily it's only a 1 byte difference. Sep 19, 2016 at 19:54 • The 96, 77 and 59 byte code versions are not found in your edit history. Edit 7 title showed 10 bytes less compared to edit 6, but the code hadn't been changed. I'm nitpicking though, great golfing! Sep 19, 2016 at 20:01 • @seshoumara I think it combined some of the edits because they were minor (just deleting a few characters). Sep 19, 2016 at 20:02 • @seshoumara I guess it actually combined them because I made multiple edits within 5 minutes of each other. Sep 19, 2016 at 20:08 ## JavaScript (ES6), 62 60 bytes f= (s,a,u=new URL(s))=>a.map(e=>u.searchParams.delete(e))&&''+u ; s.value=document.URL; <div oninput=o.textContent=f(s.value,a.value.split\n)><input id=s><br><textarea id=a></textarea><pre id=o> Edit: Saved 2 bytes thanks to @Shaggy. • You can save 5 bytes by dropping the .href at the end. Apr 20, 2017 at 14:49 • @Shaggy That wouldn't return a string... I was assuming that wasn't allowed. – Neil Apr 20, 2017 at 15:40 • It depends how you output it. For example, if you alert it or use insert it into a (text) node, as you have, it will give you the href property of the object. If you log it to the console, though, it'll give you the full object. See this Fiddle. Apr 20, 2017 at 16:01 • @Shaggy Ah, so I can definitely save 2 bytes by stringifying it, thanks. – Neil Apr 20, 2017 at 16:06 # PHP, 90 Bytes <?=trim(preg_replace("#(?<=\?|&)(".join("|",$_GET[r]).")(=.*)?(&|$)#U","",$_GET[u]),"?&");


-11 Bytes if ? or & is allowed at the end

Previous Version 140 Bytes

<?=substr($u=$_GET[u],0,strpos($u,"?")+!!$j=join("&",preg_grep("#^(".join("|",$_GET[r]).")(=|$)#",explode("&",parse_url($u)[query]),1))).$j;

• +2 bytes: The alternatives must be parenthesized, or ^/(.*|$) will be part of the first/last alternative. Sep 19, 2016 at 20:57 • -2 bytes: remove .*. or replace (=.*|$) with \b (-5). Sep 19, 2016 at 21:05
• Your regexp will look like #^foo|bar(=.*|$)# which is identical to #(^foo)|(bar=.*|bar$))#. But it should be #(foo|bar)(=.*|$)#. Sep 19, 2016 at 21:07 • @Titus You are right my fault Sep 19, 2016 at 21:08 • nice! I didn´t think of assertions; that´s why I fell back to array_map (and I was surprised how short it can turn out). Sep 19, 2016 at 23:50 # PHP, 120 110 bytes with preg_replace and array functions: (inspired by Jörg) <?=preg_replace(array_map(function($s){return"#(\\?|&)$s(=.*)?(&|$)#U";},array_slice($argv,2)),"\1",$argv[1]);


save to file, call with php <scriptname> <uri> <parametername> <parametername> ...

with parse_str and http_build_query (120 bytes):

}
url = url.replaceAll("[?&]$",""); // If we remove all of the params then we'll have a trailing ? which needs to be removed // If we remove the last param only then we could have a trailing & which also needs to be removed // We will only run into one of these scenarios return url; }  Ideone • This fails for me using your 4th, 5th, 6th, and 9th examples o.O. I'm using Java 8 though, so that could be it. Although tried the C# equivalent and it failed the same cases, so idunno. Sep 19, 2016 at 20:27 • Nevermind, messed up the way I was testing it. Sep 19, 2016 at 20:30 # C#, 377336330 328 bytes (173 alt) string n(string u,string[]r){var s=u.Split('?');if(s.Length<2)return u;var a=s[1].Contains("&")?s[1].Split('&'):new string[]{s[1]};int B=a.Length,i=0,C=i,c=B;for(;i<B;i++)foreach(var R in r)if(R==a[i].Split('=')[0]){a[i]="";c--;}var t=s[0];t+=c>0?"?":"";for(i=0;i<a.Length;i++)if(a[i]!=""){t+=a[i];C++;if(C!=c)t+="&";}return t;}  Ungolfed full program: using System; class a { static void Main() { string input = Console.ReadLine(); string url = input.Split(' ')[0]; string r = input.Split(' ')[1]; r = r.Replace("[", "").Replace("]",""); string[] remove = r.Split(','); a b = new a(); Console.WriteLine(b.n(url, remove)); } string n(string u,string[]r) { var s=u.Split('?'); if(s.Length<2)return u; var a=s[1].Contains("&")?s[1].Split('&'):new string[]{s[1]}; int B=a.Length,i=0,C=i,c=B; for(;i<B;i++) foreach(var R in r) if(R==a[i].Split('=')[0]) { a[i]=""; c--; } var t=s[0]; t+=c>0?"?":""; for(i=0;i<a.Length;i++) if(a[i]!="") { t+=a[i]; C++; if (C!=c)t+="&"; } return t; } }  Probably not very efficient, but it works I think. Alternatively, there is a 173 byte solution using @Poke's method from Java. Requires an import for Regex though, so probably can't be shorter. using System.Text.RegularExpressions;string m(string a,string[]b){foreach(var c in b)a=Regex.Replace(a,$"(?<=[?&]){c}(=[^&]*)?(&|$)","");return Regex.Replace(a,"[?&]$","");}


# Ruby, 146 140 127 119 116 113 bytes

edit 2: saved 6 bytes by using $1, $2, and $*, and 7 by changing x.split("=")[0] to x[/\w+/] edit 3: saved 6 bytes by using * instead of .join, saved 2 bytes from unnecessary spaces edit 4: saved 3 bytes by reformulating inline (changed regex to equivalent $*[1][/([^?]*)\??(.*)/,1] and put as assigned to a)
edit 5: saved 3 bytes by using ($*[2].scan(r=/\w+/)&[x[r]])[0] instead of $*[2].scan(r=/\w+/).include?(x[r])

Assuming input to the program when running it:

a,b=$*[1][/([^?]*)\??(.*)/,1],$2.split("&").reject{|x|($*[2].scan(r=/\w+/)&[x[r]])[0]}*"&" puts(b[0] ?a+"?"+b: a)  ## Explanation a,b=$*[1][/([^?]*)\??(.*)/,1],$2.split("&")  This parses the URL given on the command line, and stores the matches into $1 and $2. $*[1][/([^?]*)\??(.*)/,1] also returns the first match to store inside a, while the second match is referred to as $2 let a point to$1, and parse b into an array of arrays...

.reject { |x|


...rejecting all that...

    ($*[2].scan(r=/\w+/)&[x[r]])[0]  ...have a string before the '=' that is included in the list of names given by the second parameter... This works because we scan for words (to get the list) then get the word before the =, and see if that word is in the list with &. Since & returns an empty array on "not found" (the null set), we use the trick explained below to get nil if there are no elements in the array. otherwise we return a string, which counts as truthy, which rejects that string. }*"&"  ...and join the remaining strings together with '&' At this point, b is the GET query string for the URL. Thus, we just need to print it. puts(b[0] ?a+"?"+b: a)  This uses a trick in ruby. b[0] will be nil if b is an empty array or string. So if its truthy, (not nil or false), then there's at least one element in the array, so we need to puts a+"?"+b for the correct URL. otherwise, we just puts a, because there are no parameters to show Note: this answer assumes that ? cannot appear anywhere except to delimit the URL from the query. (according to what I read from the linked RFC) Also, this is my first golf answer :D • Welcome to PPCG! Sep 20, 2016 at 1:28 ## Pip, 46 bytes Takes the URL from stdin and the query parameters for removal from command-line args. YgqR\?.+{s:J_@^[^=]+NIyFI@>a^'&[s&'?sJ'&]}  Try it online! Explanation:  g Local variable containing list of cmdline args Y Yank into global variable y so it's available inside the function q Grab a line of stdin R\?.+{...} Do a regex replace of everything from ? on, using a callback function: s:J_@^[^=]+NIyFI@>a^'&[s&'?sJ'&] @>a^'& All but 1st char of match, split on & FI Filter on this function: _@^[^=]+ Regex match: run of non = from beginning of string @ returns a list (here, of one item), so... J Join to get a scalar NIy True if match not in y; false if in y s: Assign the filtered list to s [ ] Return a list containing: s&'? ? if s is nonempty, [] otherwise sJ'& s joined on & When used as a replacement, a list is first stringified (which, in the absence of flags, means concatenated)  ## PowerShell v3+, 115 90 bytes param($n,$z)$a,$b=$n-split'\?';($z|%{$b=$b-replace"(^|&)$_(=[^&]*)?(&|$)"});$a+"?"*!!$b+$b


Takes input $n as the URL and $z as a literal array of strings as the parameters to remove. -splits the input URL on ?, stores the first half into $a and the second into $b.

Next, $b is re-formulated by taking a loop through $z, performing a regex -replace on each banned query word to remove them. Then, outputs $a (unmodified), plus a / depending upon whether $b exists, plus a ? depending upon whether $x exists, plus $x.

# Pyth - 27 bytes

Kenny was right when he talked about the builtin to transform and then invert, it'll be very hard to right, though.

.sjK\?mj\&f!}hcT\=Qcd\&czKK


## Retina, 44 48 bytes

Crossed-out 44 is still 44. Thanks to Martin for the fix.

[?&](?>([^ =&]+))[^ &]*(?=.* \1( |$))| .* /& /?  Takes input like uri param1 param2. Try it online! ### Explanation The first replacement deletes the appropriate parameters from the query string. [?&](?>([^ =&+))[^ &]* matches a ? or &, a full parameter name, and (optionally) = and a value, storing the parameter name in capture group 1. Then (?=.* \1( |$)) is a lookahead that checks whether that parameter name appears in the list of parameters to delete. If a parameter matches these conditions, it is removed (substituted with an empty replacement).

Substitutions are non-overlapping (thanks to the lookahead) and proceed from left to right. On reaching the end of the URL, the  .* branch matches the list of parameters to delete and removes it as well.

The second replacement just makes sure the new query string starts with ? if the first parameter was deleted.

• I think this also removes parameters if a prefix of them appears in the list at the end (e.g. try retina.tryitonline.net/…). One way to fix this is to wrap group 1 in (?>...). Sep 20, 2016 at 13:47
• @MartinEnder TIL about non-backtracking subexpressions. Thanks! Sep 20, 2016 at 21:48

# Java 7, 203 bytes

String f(String u,List p)throws Exception{String[]g=u.split("\\?",2);String s="";if(g.length>1)for(String q:g[1].split("&")){if(p.indexOf(q.split("=")[0])<0){s+=s.isEmpty()?"?":"&";s+=q;}}return g[0]+s;}


Ungolfed:

  String f(String u, List p) throws Exception {
String[] g = u.split("\\?", 2);
String s = "";
if (g.length > 1) for (String q : g[1].split("&")) {
if (p.indexOf(q.split("=")[0]) < 0) {
s += s.isEmpty() ? "?" : "&";
s += q;
}
}
return g[0] + s;
}


This function passes all of the tests.

# Python, 7581 112 bytes:

def Z(A,S):import re;F=A.rindex('/');print A[:F]+re.sub('|'.join(i+'(=\d?|&)&?'for i in S),'',A[F:]).strip('&?')


A named function. Takes input in the format

D(<String>,<Array>)


and outputs a string.

Repl.it With All Test Cases!

# PHP, not competing

Heck, PHP was made for this; why not use the actual URL?

<?foreach($_GET[x]as$w)unset($_GET[$w]);
echo http,s[$_SERVER[SERVER_PORT]-443],"://",$u=$_SERVER[PHP_AUTH_USER],($p=$_SERVER[PHP_AUTH_PW])?":$p":"","@"[!$u&!$p],
"$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]?",http_build_query(\$_GET);


Save to file, call with your wanted query string plus &x[]=x&x[]=<exclude1>&x[]=<exclude2>&....

Will fail if password is 0`.