19
\$\begingroup\$

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:[email protected]:8080/?foo=1&bar=foo [foo]
> http://foo:[email protected]:8080/?bar=foo

Scoring

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

\$\endgroup\$
13
  • 1
    \$\begingroup\$ Can I get the URL and the query parameters each on separate lines? \$\endgroup\$
    – seshoumara
    Commented Sep 19, 2016 at 17:59
  • 1
    \$\begingroup\$ Can & appear anywhere other than between parameters? \$\endgroup\$
    – Riley
    Commented Sep 19, 2016 at 18:00
  • \$\begingroup\$ also can e.g. the password contain a ?? Also should the order be kept like it was? \$\endgroup\$
    – KarlKastor
    Commented Sep 19, 2016 at 18:30
  • \$\begingroup\$ @Riley No. If & is part of a query parameter it should be correctly urlencoded as %26 \$\endgroup\$
    – Poke
    Commented Sep 19, 2016 at 18:44
  • 1
    \$\begingroup\$ Apparently, http://foo:&[email protected]: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 =) \$\endgroup\$ Commented Sep 20, 2016 at 9:03

14 Answers 14

6
\$\begingroup\$

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/
\$\endgroup\$
5
  • \$\begingroup\$ In your current code edit, multiple tests from OP's question give a trailing & or ? character in the resulting URL. \$\endgroup\$
    – seshoumara
    Commented Sep 19, 2016 at 19:52
  • \$\begingroup\$ @seshoumara I'm not sure how I missed that... Luckily it's only a 1 byte difference. \$\endgroup\$
    – Riley
    Commented Sep 19, 2016 at 19:54
  • \$\begingroup\$ 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! \$\endgroup\$
    – seshoumara
    Commented Sep 19, 2016 at 20:01
  • 1
    \$\begingroup\$ @seshoumara I think it combined some of the edits because they were minor (just deleting a few characters). \$\endgroup\$
    – Riley
    Commented Sep 19, 2016 at 20:02
  • \$\begingroup\$ @seshoumara I guess it actually combined them because I made multiple edits within 5 minutes of each other. \$\endgroup\$
    – Riley
    Commented Sep 19, 2016 at 20:08
5
\$\begingroup\$

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.

\$\endgroup\$
4
  • \$\begingroup\$ You can save 5 bytes by dropping the .href at the end. \$\endgroup\$
    – Shaggy
    Commented Apr 20, 2017 at 14:49
  • \$\begingroup\$ @Shaggy That wouldn't return a string... I was assuming that wasn't allowed. \$\endgroup\$
    – Neil
    Commented Apr 20, 2017 at 15:40
  • \$\begingroup\$ 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. \$\endgroup\$
    – Shaggy
    Commented Apr 20, 2017 at 16:01
  • 1
    \$\begingroup\$ @Shaggy Ah, so I can definitely save 2 bytes by stringifying it, thanks. \$\endgroup\$
    – Neil
    Commented Apr 20, 2017 at 16:06
3
\$\begingroup\$

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;
\$\endgroup\$
6
  • \$\begingroup\$ +2 bytes: The alternatives must be parenthesized, or ^/(.*|$) will be part of the first/last alternative. \$\endgroup\$
    – Titus
    Commented Sep 19, 2016 at 20:57
  • \$\begingroup\$ -2 bytes: remove .*. or replace (=.*|$) with \b (-5). \$\endgroup\$
    – Titus
    Commented Sep 19, 2016 at 21:05
  • \$\begingroup\$ Your regexp will look like #^foo|bar(=.*|$)# which is identical to #(^foo)|(bar=.*|bar$))#. But it should be #(foo|bar)(=.*|$)#. \$\endgroup\$
    – Titus
    Commented Sep 19, 2016 at 21:07
  • \$\begingroup\$ @Titus You are right my fault \$\endgroup\$ Commented Sep 19, 2016 at 21:08
  • \$\begingroup\$ 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). \$\endgroup\$
    – Titus
    Commented Sep 19, 2016 at 23:50
2
\$\begingroup\$

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):

parse_str(end($u=explode('?',$argv[1])),$a);for($i=$argc;$i-->1;)unset($a[$argv[$i]]);echo"$u[0]?".http_build_query($a);

run with php -r <code> <uri> <parametername> <parametername> ...

\$\endgroup\$
3
  • \$\begingroup\$ parse_str? http_build_query? I'm so glad to see someone working using right tools for the job, even in code golf. Bugs that arise becuse URL/SQL query/regexp/HTML are "just strings" are as numerous as they are easily preventable. \$\endgroup\$ Commented Sep 19, 2016 at 22:52
  • \$\begingroup\$ Maybe for more inspiration. I got You \$\endgroup\$ Commented Sep 19, 2016 at 23:23
  • \$\begingroup\$ @Lynn Don´t you have anything better to do than stalking me? \$\endgroup\$
    – Titus
    Commented Sep 20, 2016 at 9:18
2
\$\begingroup\$

Java 7, 127 bytes

String a(String a,String[]b){for(String c:b)a=a.replaceAll("(?<=[?&])"+c+"(=[^&]*)?(&|$)","");return a.replaceAll("[?&]$","");}

Explanation

String sanitize(String url, String[] params) {
    for (String param : params) {
        // please don't modify function parameters in real code
        url=url.replaceAll("(?<=[?&])" // Look for a leading ? or & but don't consume it
            + param                    // Consume the key of the query param (assuming key=value syntax)
            + "(=[^&]*)?"              // Consume the value of the query param if it exists
            + "(&|$)","");             // Consume the trailing & unless we're at the end of the url and replace with nothing
    }
    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

\$\endgroup\$
2
  • \$\begingroup\$ 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. \$\endgroup\$
    – Yodle
    Commented Sep 19, 2016 at 20:27
  • 1
    \$\begingroup\$ Nevermind, messed up the way I was testing it. \$\endgroup\$
    – Yodle
    Commented Sep 19, 2016 at 20:30
2
\$\begingroup\$

C#, 377 336 330 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,"[?&]$","");}
\$\endgroup\$
2
\$\begingroup\$

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

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Welcome to PPCG! \$\endgroup\$
    – acrolith
    Commented Sep 20, 2016 at 1:28
1
\$\begingroup\$

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)
\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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

Test Suite.

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ 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 (?>...). \$\endgroup\$ Commented Sep 20, 2016 at 13:47
  • \$\begingroup\$ @MartinEnder TIL about non-backtracking subexpressions. Thanks! \$\endgroup\$
    – DLosc
    Commented Sep 20, 2016 at 21:48
0
\$\begingroup\$

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.

\$\endgroup\$
0
\$\begingroup\$

Python, 75 81 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!

\$\endgroup\$
0
\$\begingroup\$

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>&....

May fail on username and password (depending on wether your browser removes them or not).
Will fail if password is 0.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.