1 /*******************************************************************************
2  * Copyright (c) 2003, 2017 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.ui.internal.intro.impl.model.url;
15 
16 import java.io.ByteArrayOutputStream;
17 import java.net.MalformedURLException;
18 import java.net.URL;
19 import java.nio.charset.StandardCharsets;
20 import java.util.Properties;
21 
22 import org.eclipse.ui.internal.intro.impl.util.Log;
23 
24 /**
25  * A parser that knows how to parser OOBE action URLs. If URL is a valid intro
26  * url, it will create an instance of the IntroURL class.
27  */
28 public class IntroURLParser {
29 
30 	// private String url_string = null;
31 	private boolean hasProtocol = false;
32 	private boolean isIntroUrl = false;
33 
34 	private URL url_inst;
35 
36 	/**
37 	 * Constructor that gets the URL to parse.
38 	 */
IntroURLParser(String url)39 	public IntroURLParser(String url) {
40 		// create a URL instance, and parser it for parameters.
41 		parseUrl(url);
42 	}
43 
parseUrl(String url)44 	private void parseUrl(String url) {
45 		if (url == null)
46 			return;
47 		url_inst = null;
48 		try {
49 			url_inst = new URL(url);
50 		} catch (MalformedURLException e) {
51 			// not a valid URL. set state.
52 			return;
53 		}
54 
55 		if (url_inst.getProtocol() != null) {
56 			// URL has some valid protocol. Check to see if it is an intro url.
57 			hasProtocol = true;
58 			isIntroUrl = isIntroUrl(url_inst);
59 			return;
60 		}
61 
62 		// not an Intro URL. do nothing.
63 		return;
64 	}
65 
66 
67 	/**
68 	 * @return Returns the hasProtocol.
69 	 */
hasProtocol()70 	public boolean hasProtocol() {
71 		return hasProtocol;
72 	}
73 
74 	/**
75 	 * @return Returns the isIntroUrl.
76 	 */
hasIntroUrl()77 	public boolean hasIntroUrl() {
78 		return isIntroUrl;
79 	}
80 
81 
82 	/**
83 	 * @return Returns the currebt url Protocol.
84 	 */
getProtocol()85 	public String getProtocol() {
86 		return url_inst.getProtocol();
87 	}
88 
89 
90 	/**
91 	 * @return Returns the currebt url Protocol.
92 	 */
getHost()93 	public String getHost() {
94 		return url_inst.getHost();
95 	}
96 
97 
98 	/**
99 	 * Checks to see if tha passed URL is an Intro URL. An intro URL is an http
100 	 * URL that has the intro plugin id as a host. eg:
101 	 * "http://org.eclipse.ui.intro/test".
102 	 *
103 	 * @param url
104 	 * @return true if url is an intro URL.
105 	 */
isIntroUrl(URL url)106 	private boolean isIntroUrl(URL url) {
107 		if (!url.getProtocol().equalsIgnoreCase(IntroURL.INTRO_PROTOCOL))
108 			// quick exit. If it is not http, url is not an Intro url.
109 			return false;
110 
111 		if (url.getHost().equalsIgnoreCase(IntroURL.INTRO_HOST_ID))
112 			return true;
113 
114 		return false;
115 	}
116 
117 
118 
119 	/**
120 	 * @return Returns the introURL. Will be null if the parsed URL is not an
121 	 *         Intro URL.
122 	 */
getIntroURL()123 	public IntroURL getIntroURL() {
124 		IntroURL introURL = null;
125 		if (isIntroUrl) {
126 			// valid intro URL. Extract the action and parameters.
127 			String action = getPathAsAction(url_inst);
128 			Properties parameters = getQueryParameters(url_inst);
129 
130 			// class instance vars are already populated by now.
131 			introURL = new IntroURL(action, parameters);
132 		}
133 		return introURL;
134 	}
135 
136 
137 
138 	/**
139 	 * Retruns the path attribute of the passed URL, stripped out of the leading
140 	 * "/". Returns null if the url does not have a path.
141 	 *
142 	 * @param url
143 	 * @return
144 	 */
getPathAsAction(URL url)145 	private String getPathAsAction(URL url) {
146 		// get possible action.
147 		String action = url.getPath();
148 		// remove leading "/" from path.
149 		if (action != null)
150 			action = action.substring(1);
151 		return action;
152 	}
153 
154 	/**
155 	 * Retruns the Query part of the URL as an instance of a Properties class.
156 	 *
157 	 * @param url
158 	 * @return
159 	 */
getQueryParameters(URL url)160 	public Properties getQueryParameters(URL url) {
161 		// parser all query parameters.
162 		Properties properties = new Properties();
163 		String query = url.getQuery();
164 		if (query == null)
165 			// we do not have any parameters in this URL, return an empty
166 			// Properties instance.
167 			return properties;
168 
169 		// now extract the key/value pairs from the query.
170 		String[] params = query.split("&"); //$NON-NLS-1$
171 		for (int i = 0; i < params.length; i++) {
172 			// for every parameter, ie: key=value pair, create a property
173 			// entry. we know we have the key as the first string in the array,
174 			// and the value as the second array.
175 			String[] keyValuePair = params[i].split("="); //$NON-NLS-1$
176 			if (keyValuePair.length != 2) {
177 				Log.warning("Ignoring the following Intro URL parameter: " //$NON-NLS-1$
178 						+ params[i]);
179 				continue;
180 			}
181 
182 			String key = urlDecode(keyValuePair[0]);
183 			if (key == null) {
184 				Log.warning("Failed to URL decode key: " + keyValuePair[0]); //$NON-NLS-1$
185 				continue;
186 			}
187 
188 			String value = urlDecode(keyValuePair[1]);
189 			if (value == null) {
190 				Log.warning("Failed to URL decode value: " + keyValuePair[1]); //$NON-NLS-1$
191 				continue;
192 			}
193 
194 			properties.setProperty(key, value);
195 		}
196 		return properties;
197 	}
198 
199 
200 	/*
201 	 * Note: This was copied and adapted from org.eclipse.help.internal.util.URLCoder
202 	 */
urlDecode(String encodedURL)203 	private static String urlDecode(String encodedURL) {
204 		int len = encodedURL.length();
205 		ByteArrayOutputStream os = new ByteArrayOutputStream(len);
206 
207 		for (int i = 0; i < len;) {
208 			switch (encodedURL.charAt(i)) {
209 			case '%':
210 				if (len >= i + 3) {
211 					os.write(Integer.parseInt(encodedURL.substring(i + 1, i + 3), 16));
212 				}
213 				i += 3;
214 				break;
215 			case '+': // exception from standard
216 				os.write(' ');
217 				i++;
218 				break;
219 			default:
220 				os.write(encodedURL.charAt(i++));
221 				break;
222 			}
223 		}
224 		return new String(os.toByteArray(), StandardCharsets.UTF_8);
225 	}
226 
227 }
228