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

\$\endgroup\$
  • 1
    \$\begingroup\$ Can I get the URL and the query parameters each on separate lines? \$\endgroup\$ – seshoumara Sep 19 '16 at 17:59
  • 1
    \$\begingroup\$ Can & appear anywhere other than between parameters? \$\endgroup\$ – Riley Sep 19 '16 at 18:00
  • \$\begingroup\$ also can e.g. the password contain a ?? Also should the order be kept like it was? \$\endgroup\$ – KarlKastor Sep 19 '16 at 18:30
  • \$\begingroup\$ @Riley No. If & is part of a query parameter it should be correctly urlencoded as %26 \$\endgroup\$ – Poke Sep 19 '16 at 18:44
  • 1
    \$\begingroup\$ 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 =) \$\endgroup\$ – n̴̖̋h̷͉̃a̷̭̿h̸̡̅ẗ̵̨́d̷̰̀ĥ̷̳ Sep 20 '16 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\$
  • \$\begingroup\$ In your current code edit, multiple tests from OP's question give a trailing & or ? character in the resulting URL. \$\endgroup\$ – seshoumara Sep 19 '16 at 19:52
  • \$\begingroup\$ @seshoumara I'm not sure how I missed that... Luckily it's only a 1 byte difference. \$\endgroup\$ – Riley Sep 19 '16 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 Sep 19 '16 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 Sep 19 '16 at 20:02
  • \$\begingroup\$ @seshoumara I guess it actually combined them because I made multiple edits within 5 minutes of each other. \$\endgroup\$ – Riley Sep 19 '16 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\$
  • \$\begingroup\$ You can save 5 bytes by dropping the .href at the end. \$\endgroup\$ – Shaggy Apr 20 '17 at 14:49
  • \$\begingroup\$ @Shaggy That wouldn't return a string... I was assuming that wasn't allowed. \$\endgroup\$ – Neil Apr 20 '17 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 Apr 20 '17 at 16:01
  • 1
    \$\begingroup\$ @Shaggy Ah, so I can definitely save 2 bytes by stringifying it, thanks. \$\endgroup\$ – Neil Apr 20 '17 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\$
  • \$\begingroup\$ +2 bytes: The alternatives must be parenthesized, or ^/(.*|$) will be part of the first/last alternative. \$\endgroup\$ – Titus Sep 19 '16 at 20:57
  • \$\begingroup\$ -2 bytes: remove .*. or replace (=.*|$) with \b (-5). \$\endgroup\$ – Titus Sep 19 '16 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 Sep 19 '16 at 21:07
  • \$\begingroup\$ @Titus You are right my fault \$\endgroup\$ – Jörg Hülsermann Sep 19 '16 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 Sep 19 '16 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\$
  • \$\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\$ – Daerdemandt Sep 19 '16 at 22:52
  • \$\begingroup\$ Maybe for more inspiration. I got You \$\endgroup\$ – Jörg Hülsermann Sep 19 '16 at 23:23
  • \$\begingroup\$ @Lynn Don´t you have anything better to do than stalking me? \$\endgroup\$ – Titus Sep 20 '16 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\$
  • \$\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 Sep 19 '16 at 20:27
  • 1
    \$\begingroup\$ Nevermind, messed up the way I was testing it. \$\endgroup\$ – Yodle Sep 19 '16 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\$
  • 2
    \$\begingroup\$ Welcome to PPCG! \$\endgroup\$ – acrolith Sep 20 '16 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\$
  • \$\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\$ – Martin Ender Sep 20 '16 at 13:47
  • \$\begingroup\$ @MartinEnder TIL about non-backtracking subexpressions. Thanks! \$\endgroup\$ – DLosc Sep 20 '16 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, privacy policy and cookie policy

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