ACC SHELL

Path : /usr/share/YaST2/modules/
File Upload :
Current File : //usr/share/YaST2/modules/URL.ycp

/**
 * File:	modules/URL.ycp
 * Package:	yast2
 * Summary:	Manipulate and Parse URLs
 * Authors:	Michal Svec <msvec@suse.cz>
 *		Anas Nashif <nashif@suse.cz>
 * Flags:	Stable
 *
 * $Id: URL.ycp 57013 2009-04-28 15:09:55Z lslezak $
 */

{

module "URL";
textdomain "base";

import "Hostname";
import "String";
import "IP";
import "URLRecode";

/**
 * TODO:
 * - read URI(3)
 * - esp. compare the regex mentioned in the URI(3) with ours:
 *   my($scheme, $authority, $path, $query, $fragment) =
 *   $uri =~ m|^(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?|;
 */

/**
 * Valid characters in URL
 */
global string ValidChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.:_-/%";


/**
 * Transform map used for (un)escaping characters in username/password part of an URL.
 * It doesn't contain '%' because this character must be used in a particular
 * order (the first or the last) during processing
 */
global map<string, string> transform_map_passwd = $[
    ";":"%3b",
    "/":"%2f",
    "?":"%3f",
    ":":"%3a",
    "@":"%40",
    "&":"%26",
    "=":"%3d",
    "+":"%2b",
    "$":"%24",
    ",":"%2c",
    " ":"%20"
];

/**
 * Transform map used for (un)escaping characters in file location part of an URL.
 * It doesn't contain '%' because this character must be used in a particular
 * order (the first or the last) during processing
 */
global map<string, string> transform_map_filename = $[
    ";":"%3b",
    "?":"%3f",
    ":":"%3a",
    "@":"%40",
    "&":"%26",
    "=":"%3d",
    "+":"%2b",
    "$":"%24",
    ",":"%2c",
    " ":"%20"
];

/**
 * Transform map used for (un)escaping characters in query part of a URL.
 * It doesn't contain '%' because this character must be used in a particular
 * order (the first or the last) during processing
 */
global map<string, string> transform_map_query = $[
    ";":"%3b",
    "?":"%3f",
    "@":"%40",
    "+":"%2b",
    "$":"%24",
    ",":"%2c",
    " ":"%20"
];

/**
 * Escape reserved characters in string used as a part of URL (e.g. '%25' => '%', '%40' => '@'...)
 *
 * @param in input string to escape
 * @param transformation map
 * @return string unescaped string
 *
 * @example
 *	URL::UnEscapeString ("http%3a%2f%2fsome.nice.url%2f%3awith%3a%2f%24p#ci%26l%2fch%40rs%2f", URL::transform_map_passwd)
 *		-> http://some.nice.url/:with:/$p#ci&l/ch@rs/
 */

global string UnEscapeString(string in, map<string,string> transform) {

    if (in == nil || in == "")
    {
	return "";
    }

    // replace the other reserved characters
    foreach(string tgt, string src, transform, {
	    // replace both upper and lower case escape sequences
	    in = String::Replace(in, tolower(src), tgt);
	    in = String::Replace(in, toupper(src), tgt);
	}
    );

    // replace % at the end
    in = String::Replace(in, "%25", "%");

    return in;
}

/**
 * Escape reserved characters in string used as a part of URL (e.g. '%' => '%25', '@' => '%40'...)
 *
 * @param in input string to escape
 * @param transformation map
 * @return string escaped string
 *
 * @example
 *	URL::EscapeString ("http://some.nice.url/:with:/$p#ci&l/ch@rs/", URL::transform_map_passwd)
 *		-> http%3a%2f%2fsome.nice.url%2f%3awith%3a%2f%24p#ci%26l%2fch%40rs%2f
 */

global string EscapeString(string in, map<string,string> transform) {
    string ret = "";

    if (in == nil || in == "")
    {
	return ret;
    }

    // replace % at first
    ret = mergestring(splitstring(in, "%"), "%25");

    // replace the other reserved characters
    foreach(string src, string tgt, transform, {
	    ret = mergestring(splitstring(ret, src), tgt);
	}
    );

    return ret;
}

global map <string, string> MakeMapFromParams (string params);
global string MakeParamsFromMap (map <string, string> params_map);

/**
 * Tokenize URL
 * @param url URL to be parsed
 * @return URL split to tokens
 * @example Parse("http://name:pass@www.suse.cz:80/path/index.html?question#part") ->
 *     $[
 *         "scheme"  : "http",
 *         "host"    : "www.suse.cz"
 *         "port"    : "80",
 *         "path"    : /path/index.html",
 *         "user"    : "name",
 *         "pass"    : "pass",
 *         "query"   : "question",
 *         "fragment": "part"
 *     ]
 */
global define map Parse(string url) ``{

    y2debug("url=%1", url);

    /* We don't parse empty URLs */
    if(url == nil || size(url) < 1) return $[];

    /* Extract basic URL parts: scheme://host/path?question#part */
    list rawtokens = regexptokenize(url, "^" +
	/* 0,1: http:// */
	"(([^:/?#]+):[/]{0,2})?" +
	/* 2: user:pass@www.suse.cz:23 */
	"([^/?#]*)?" +
	/* 3: /some/path */
	"([^?#]*)?" +
	/* 4,5: ?question */
	"(\\?([^#]*))?" +
	/* 6,7: #fragment */
	"(#(.*))?"
    );
    y2debug("rawtokens=%1", rawtokens);
    map tokens = $[];
    tokens["scheme"] = rawtokens[1]:"";
    string pth = rawtokens[3]:"";
    if (tokens["scheme"]:"" == "ftp")
    {
	if (substring (pth, 0, 4) == "/%2f")
	{
	    pth = "/" + substring (pth, 4);
	}
	else if (pth != "")
	{
	    pth = substring (pth, 1);
	}
    }
    tokens["path"] = URLRecode::UnEscape(pth);
    tokens["query"] = URLRecode::UnEscape(rawtokens[5]:"");
    tokens["fragment"] = URLRecode::UnEscape(rawtokens[7]:"");

    /* Extract username:pass@host:port */
    list userpass = regexptokenize(rawtokens[2]:"", "^" +
	/* 0,1,2,3: user:pass@ */
	"(([^@:]+)(:([^@:]+))?@)?" +
	/* 4,5,6,7: hostname|[xxx] */
	"(([^:@]+))" +
	// FIXME "(([^:@]+)|(\\[([^]]+)\\]))" +
	/* 8,9: port */
	"(:([^:@]+))?"
    );
    y2debug("userpass=%1", userpass);

    tokens["user"] = URLRecode::UnEscape(userpass[1]:"");
    tokens["pass"] = URLRecode::UnEscape(userpass[3]:"");
    tokens["port"] = userpass[7]:"";

    if(userpass[5]:"" != "")
	tokens["host"] = userpass[5]:"";
    else
	tokens["host"] = userpass[7]:"";

    string hostport6 = substring(rawtokens[2]:"", size(userpass[0]:""));
    y2debug("hostport6: %1", hostport6);

    // check if there is an IPv6 address
    string host6 = regexpsub(hostport6, "^\\[(.*)\\]", "\\1");

    if (host6 != nil && host6 != "")
    {
	y2milestone("IPv6 host detected: %1", host6);
	tokens["host"] = host6;
	string port6 = regexpsub(hostport6, "^\\[.*\\]:(.*)", "\\1");
	y2debug("port: %1", port6);
	tokens["port"] = (port6 != nil) ? port6 : "";
    }

    // some exceptions for samba scheme (there is optional extra option "domain")
    if (tokens["scheme"]:""=="samba" || tokens["scheme"]:""=="smb")
    {
	// Note: CUPS uses different URL syntax for Samba printers:
	//     smb://username:password@workgroup/server/printer
	// Fortunately yast2-printer does not use URL.ycp, so we can safely support libzypp syntax only:
	//     smb://username:passwd@servername/share/path/on/the/share?workgroup=mygroup

	map<string,string> options = MakeMapFromParams(tokens["query"]:"");

	if (haskey(options, "workgroup"))
	{
	    tokens["domain"] = options["workgroup"]:"";
	}
    }
    y2debug("tokens=%1", tokens);
    return tokens;
}

/**
 * Check URL
 * @param url URL to be checked
 * @return true if correct
 * @see RFC 2396 (updated by RFC 2732)
 * @see also perl-URI: URI(3)
 */
global define boolean Check(string url) ``{
    /* We don't allow empty URLs */
    if(url == nil || size(url) < 1) return false;

    /* We don't allow URLs with spaces */
    if(search(url, " ") != nil) return false;

    map tokens = Parse(url);

    y2debug("tokens: %1", tokens);

    /* Check "scheme"  : "http" */
    if(!regexpmatch(tokens["scheme"]:"", "^[[:alpha:]]*$"))
	return false;

    /* Check "host"    : "www.suse.cz" */
    if((!Hostname::CheckFQ(tokens["host"]:"") && !IP::Check(tokens["host"]:""))
            && tokens["host"]:"" != "")
	return false;

    /* Check "path"    : /path/index.html" */

    /* Check "port"    : "80" */
    if(!regexpmatch(tokens["port"]:"", "^[0-9]*$"))
	return false;

    /* Check "user"    : "name" */

    /* Check "pass"    : "pass" */

    /* Check "query"   : "question" */

    /* Check "fragment": "part" */

    return true;
}


/**
 * Build URL from tokens as parsed with Parse
 * @param map token as returned from Parse
 * @return string url, empty string if invalid data is used to build the url.
 * @see RFC 2396 (updated by RFC 2732)
 * @see also perl-URI: URI(3)
 */
global define string Build (map tokens) ``{
	string url = "";
	string userpass = "";

	y2debug("URL::Build(): input: %1", tokens);

	if(regexpmatch(tokens["scheme"]:"", "^[[:alpha:]]*$"))
	{
/*	 if (tokens["scheme"]:"" == "samba") url="smb";
		else*/ url = tokens["scheme"]:"";
	}
	y2debug("url: %1", url);
	if (tokens["user"]:"" != "")
	{
		userpass = URLRecode::EscapePassword(tokens["user"]:"");
		y2milestone("Escaped username '%1' => '%2'", tokens["user"]:"", userpass);
	}
	if (size(userpass) != 0 && tokens["pass"]:"" != "" )
	{
		userpass = sformat("%1:%2", userpass, URLRecode::EscapePassword(tokens["pass"]:"") );
	}
	if (size(userpass) > 0 ) userpass = userpass + "@";

	url = sformat("%1://%2", url, userpass);
	y2debug("url: %1", url);

	if(Hostname::CheckFQ(tokens["host"]:"") || IP::Check(tokens["host"]:""))
	{
	    // enclose an IPv6 address in square brackets
	    url = (IP::Check6(tokens["host"]:"")) ?
		sformat("%1[%2]", url, tokens["host"]:"") :
		sformat("%1%2", url, tokens["host"]:"");
	}
	y2debug("url: %1", url);

	if (regexpmatch(tokens["port"]:"", "^[0-9]*$") && tokens["port"]:"" != "")
	{
		url = sformat("%1:%2", url, tokens["port"]:"");
	}
	y2debug("url: %1", url);

	// path is not empty and doesn't start with "/"
	if (tokens["path"]:"" != "" && ! regexpmatch (tokens["path"]:"", "^/"))
		url = sformat("%1/%2", url, URLRecode::EscapePath(tokens["path"]:""));
	// patch is not empty and starts with "/"
	else if (tokens["path"]:"" != "" && regexpmatch (tokens["path"]:"", "^/"))
	{
		while (substring (tokens["path"]:"", 0, 2) == "//")
		    tokens["path"] = substring (tokens["path"]:"", 1);
		if (tokens["scheme"]:"" == "ftp") {
		    url = sformat("%1/%%2f%2", url, substring(URLRecode::EscapePath(tokens["path"]:""), 1));
		}
		else {
		    url = sformat("%1%2", url, URLRecode::EscapePath(tokens["path"]:""));
		}
	}
	y2debug("url: %1", url);


	map<string,string> query_map = MakeMapFromParams(tokens["query"]:"");

	if (tokens["scheme"]:"" == "smb" && size(tokens["domain"]:"")>0 && query_map["workgroup"]:"" != tokens["domain"]:"")
	{
	    query_map["workgroup"] = tokens["domain"]:"";

	    tokens["query"] = MakeParamsFromMap(query_map);
	}

	if (tokens["query"]:"" != "" )
		url = sformat("%1?%2", url, URLRecode::EscapeQuery(tokens["query"]:""));

	if (tokens["fragment"]:"" != "" )
		url = sformat("%1#%2", url, URLRecode::EscapePassword(tokens["fragment"]:""));
	y2debug("url: %1", url);

	if (!Check(url)) {
		y2error("Invalid URL: %1", url);
		return "";
	}

	y2debug("URL::Build(): result: %1", url);

	return url;

}

/**
 * Format URL - truncate the middle part of the directory to fit to the requested lenght.
 *
 * Elements in the middle of the path specification are replaced by ellipsis (...).
 * The result migth be longer that requested size if other URL parts are longer than the requested size.
 * If the requested size is greater than size of the full URL then full URL is returned.
 * Only path element of the URL is changed the other parts are not modified (e.g. protocol name)
 *
 * @example FormatURL("http://download.opensuse.org/very/log/path/which/will/be/truncated/target_file", 45)
&nbsp;&nbsp;&nbsp;&nbsp;-> "http://download.opensuse.org/.../target_file"
 * @example FormatURL("http://download.opensuse.org/very/log/path/which/will/be/truncated/target_file", 60)
&nbsp;&nbsp;&nbsp;&nbsp;-> "http://download.opensuse.org/very/.../be/truncated/target_file"
 *
 * @param tokens parsed URL
 * @see Parse should be used to convert URL string to a map (tokens parameter)
 * @param len requested maximum lenght of the output string
 * @return string Truncated URL
 */
global string FormatURL(map tokens, integer len)
{
    string ret = Build(tokens);

    // full URL is shorter than requested, no truncation needed
    if (size(ret) <= len)
    {
	return ret;
    }

    // it's too long, some parts must be removed
    string pth = tokens["path"]:"";
    tokens["path"] = "";

    string no_path = Build(tokens);
    // size for the directory part
    integer dir_size = len - size(no_path);

    // remove the path in the middle
    string new_path = String::FormatFilename(pth, dir_size);

    // build the url with the new path
    tokens["path"] = new_path;
    return Build(tokens);
}

/*
y2milestone("%1", Parse("http://a:b@www.suse.cz:33/ahoj/nekde?neco#blah"));
y2milestone("%1", Parse("ftp://www.suse.cz/ah"));
y2milestone("%1", Parse("ftp://www.suse.cz:22/ah"));
y2milestone("%1", Parse("www.suse.cz/ah"));

y2milestone("%1", Check("http://a:b@www.suse.cz:33/ahoj/nekde?neco#blah"));
y2milestone("%1", Check("ftp://www.suse.cz/ah"));
y2milestone("%1", Check("ftp://www.suse.cz:22/ah"));
y2milestone("%1", Check("www.suse.cz/ah"));
y2milestone("%1", Check("www.suse.cz ah"));
y2milestone("%1", Check(""));
y2milestone("%1", Check(nil));
*/

/**
 * Reads list of HTTP params and returns them as map.
 * (Useful also for cd:/, dvd:/, nfs:/ ... params)
 * Neither keys nor values are HTML-unescaped, see UnEscapeString().
 *
 * @params string params
 * @return map <string, string> params
 *
 * @example
 *      MakeMapFromParams ("device=sda3&login=aaa&password=bbb") -> $[
 *              "device"   : "sda3",
 *              "login"    : "aaa",
 *              "password" : "bbb"
 *      ]
 */
global map <string, string> MakeMapFromParams (string params) {
    // Error
    if (params == nil) {
	y2error ("Erroneous (nil) params!");
	return nil;
    // Empty
    } else if (params == "") {
	return $[];
    }

    list <string> params_list = splitstring (params, "&");

    params_list = filter (string one_param, params_list, {
        return (one_param != "" && one_param != nil);
    });

    map <string, string> ret = $[];
    integer eq_pos = nil;
    string opt = "";
    string val = "";

    foreach (string one_param, params_list, {
        eq_pos = search (one_param, "=");

        if (eq_pos == nil) {
            ret[one_param] = "";
        } else {
            opt = substring (one_param, 0, eq_pos);
            val = substring (one_param, (eq_pos + 1));

            ret[opt] = val;
        }
    });

    return ret;
}

/**
 * Returns string made of HTTP params. It's a reverse function to MakeMapFromParams().
 * Neither keys nor values are HTML-escaped, use EscapeString() if needed.
 *
 * @param map <string, string>
 *
 * @see MakeMapFromParams
 *
 * @example
 *   MakeMapFromParams ($[
 *     "param1" : "a",
 *     "param2" : "b",
 *     "param3" : "c",
 *   ]) -> "param1=a&param2=b&param3=c"
 */
global string MakeParamsFromMap (map <string, string> params_map) {
    // ["key1=value1", "key2=value2", ...] -> "key1=value1&key2=value2"
    return mergestring (
	// ["key" : "value", ...] -> ["key=value", ...]
	maplist (string key, string value, params_map, {
	    if (value == nil) {
		y2warning ("Empty value for key %1", key);
		value = "";
	    }

	    if (key == nil || key == "") {
		y2error ("Empty key (will be skipped)");
		return "";
	    }

	    // "key=value"
	    return sformat ("%1=%2", key, value);
	}),
	"&"
    );
}

/**
 * Hide password in an URL - replaces the password in the URL by 'PASSWORD' string.
 * If there is no password in the URL the original URL is returned.
 * It should be used when an URL is logged to y2log or when it is displayed to user.
 * @param url original URL
 * @return string new URL with 'PASSWORD' password or unmodified URL if there is no password
 */
global string HidePassword(string url) {
    // Url::Build(Url::Parse) transforms the URL too much, see #247249#c41
    // replace ://user:password@ by ://user:PASSWORD@
    string subd = regexpsub (url, "(.*)(://[^/:]*):[^/@]*@(.*)", "\\1\\2:PASSWORD@\\3");
    return subd == nil? url: subd;
}

/**
 * Hide password token in parsed URL (by URL::Parse()) - the password is replaced by 'PASSWORD' string.
 * Similar to HidePassword() but uses a parsed URL as the input.
 * @param tokens input
 * @return map map with replaced password
 */
global map HidePasswordToken(map tokens)
{
    map ret = tokens;

    // hide the password if it's there
    if (haskey(ret, "pass") && size(ret["pass"]:"") > 0)
    {
	ret["pass"] = "PASSWORD";
    }

    return ret;
}

}

ACC SHELL 2018