r106262 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r106261‎ | r106262 | r106263 >
Date:22:20, 14 December 2011
Author:neilk
Status:ok (Comments)
Tags:
Comment:
MFT r99944, r99446, 102616 update mediawiki.Uri.js to handle protocol-relative URIs and other fixes
Modified paths:
  • /branches/wmf/1.18wmf1/resources/mediawiki/mediawiki.Uri.js (modified) (history)

Diff [purge]

Index: branches/wmf/1.18wmf1/resources/mediawiki/mediawiki.Uri.js
@@ -56,7 +56,7 @@
5757 *
5858 */
5959
60 -( function( $ ) {
 60+( function( $, mw ) {
6161
6262 /**
6363 * Function that's useful when constructing the URI string -- we frequently encounter the pattern of
@@ -89,172 +89,213 @@
9090 'host', // www.test.com
9191 'port', // 81
9292 'path', // /dir/dir.2/index.htm
93 - 'query', // q1=0&&test1&test2=value (will become { q1: 0, test1: '', test2: 'value' } )
 93+ 'query', // q1=0&&test1&test2=value (will become { q1: '0', test1: '', test2: 'value' } )
9494 'fragment' // top
9595 ];
9696
97 - /**
98 - * Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse.
99 - * @constructor
100 - * @param {!Object|String} URI string, or an Object with appropriate properties (especially another URI object to clone). Object must have non-blank 'protocol', 'host', and 'path' properties.
101 - * @param {Boolean} strict mode (when parsing a string)
102 - */
103 - mw.Uri = function( uri, strictMode ) {
104 - strictMode = !!strictMode;
105 - if ( uri !== undefined && uri !== null || uri !== '' ) {
106 - if ( typeof uri === 'string' ) {
107 - this._parse( uri, strictMode );
108 - } else if ( typeof uri === 'object' ) {
109 - var _this = this;
110 - $.each( properties, function( i, property ) {
111 - _this[property] = uri[property];
112 - } );
113 - if ( this.query === undefined ) {
114 - this.query = {};
115 - }
116 - }
117 - }
118 - if ( !( this.protocol && this.host && this.path ) ) {
119 - throw new Error( 'Bad constructor arguments' );
120 - }
121 - };
12297
12398 /**
124 - * Standard encodeURIComponent, with extra stuff to make all browsers work similarly and more compliant with RFC 3986
125 - * Similar to rawurlencode from PHP and our JS library mw.util.rawurlencode, but we also replace space with a +
126 - * @param {String} string
127 - * @return {String} encoded for URI
 99+ * We use a factory to inject a document location, for relative URLs, including protocol-relative URLs.
 100+ * so the library is still testable & purely functional.
128101 */
129 - mw.Uri.encode = function( s ) {
130 - return encodeURIComponent( s )
131 - .replace( /!/g, '%21').replace( /'/g, '%27').replace( /\(/g, '%28')
132 - .replace( /\)/g, '%29').replace( /\*/g, '%2A')
133 - .replace( /%20/g, '+' );
134 - };
 102+ mw.UriRelative = function( documentLocation ) {
135103
136 - /**
137 - * Standard decodeURIComponent, with '+' to space
138 - * @param {String} string encoded for URI
139 - * @return {String} decoded string
140 - */
141 - mw.Uri.decode = function( s ) {
142 - return decodeURIComponent( s ).replace( /\+/g, ' ' );
143 - };
144 -
145 - mw.Uri.prototype = {
146 -
147104 /**
148 - * Parse a string and set our properties accordingly.
149 - * @param {String} URI
150 - * @param {Boolean} strictness
151 - * @return {Boolean} success
 105+ * Constructs URI object. Throws error if arguments are illegal/impossible, or otherwise don't parse.
 106+ * @constructor
 107+ * @param {Object|String} URI string, or an Object with appropriate properties (especially another URI object to clone).
 108+ * Object must have non-blank 'protocol', 'host', and 'path' properties.
 109+ * @param {Object|Boolean} Object with options, or (backwards compatibility) a boolean for strictMode
 110+ * - strictMode {Boolean} Trigger strict mode parsing of the url. Default: false
 111+ * - overrideKeys {Boolean} Wether to let duplicate query parameters override eachother (true) or automagically
 112+ * convert to an array (false, default).
152113 */
153 - _parse: function( str, strictMode ) {
154 - var matches = parser[ strictMode ? 'strict' : 'loose' ].exec( str );
155 - var uri = this;
156 - $.each( properties, function( i, property ) {
157 - uri[ property ] = matches[ i+1 ];
158 - } );
 114+ function Uri( uri, options ) {
 115+ options = typeof options === 'object' ? options : { strictMode: !!options };
 116+ options = $.extend( {
 117+ strictMode: false,
 118+ overrideKeys: false
 119+ }, options );
159120
160 - // uri.query starts out as the query string; we will parse it into key-val pairs then make
161 - // that object the "query" property.
162 - // we overwrite query in uri way to make cloning easier, it can use the same list of properties.
163 - var q = {};
164 - // using replace to iterate over a string
165 - if ( uri.query ) {
166 - uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ($0, $1, $2, $3) {
167 - if ( $1 ) {
168 - var k = mw.Uri.decode( $1 );
169 - var v = ( $2 === '' || $2 === undefined ) ? null : mw.Uri.decode( $3 );
170 - if ( typeof q[ k ] === 'string' ) {
171 - q[ k ] = [ q[ k ] ];
172 - }
173 - if ( typeof q[ k ] === 'object' ) {
174 - q[ k ].push( v );
175 - } else {
176 - q[ k ] = v;
177 - }
 121+ if ( uri !== undefined && uri !== null || uri !== '' ) {
 122+ if ( typeof uri === 'string' ) {
 123+ this._parse( uri, options );
 124+ } else if ( typeof uri === 'object' ) {
 125+ var _this = this;
 126+ $.each( properties, function( i, property ) {
 127+ _this[property] = uri[property];
 128+ } );
 129+ if ( this.query === undefined ) {
 130+ this.query = {};
178131 }
179 - } );
 132+ }
180133 }
181 - this.query = q;
182 - },
183134
184 - /**
185 - * Returns user and password portion of a URI.
186 - * @return {String}
187 - */
188 - getUserInfo: function() {
189 - return cat( '', this.user, cat( ':', this.password, '' ) );
190 - },
 135+ // protocol-relative URLs
 136+ if ( !this.protocol ) {
 137+ this.protocol = defaultProtocol;
 138+ }
191139
 140+ if ( !( this.protocol && this.host && this.path ) ) {
 141+ throw new Error( 'Bad constructor arguments' );
 142+ }
 143+ }
 144+
192145 /**
193 - * Gets host and port portion of a URI.
194 - * @return {String}
 146+ * Standard encodeURIComponent, with extra stuff to make all browsers work similarly and more compliant with RFC 3986
 147+ * Similar to rawurlencode from PHP and our JS library mw.util.rawurlencode, but we also replace space with a +
 148+ * @param {String} string
 149+ * @return {String} encoded for URI
195150 */
196 - getHostPort: function() {
197 - return this.host + cat( ':', this.port, '' );
198 - },
 151+ Uri.encode = function( s ) {
 152+ return encodeURIComponent( s )
 153+ .replace( /!/g, '%21').replace( /'/g, '%27').replace( /\(/g, '%28')
 154+ .replace( /\)/g, '%29').replace( /\*/g, '%2A')
 155+ .replace( /%20/g, '+' );
 156+ };
199157
200158 /**
201 - * Returns the userInfo and host and port portion of the URI.
202 - * In most real-world URLs, this is simply the hostname, but it is more general.
203 - * @return {String}
 159+ * Standard decodeURIComponent, with '+' to space
 160+ * @param {String} string encoded for URI
 161+ * @return {String} decoded string
204162 */
205 - getAuthority: function() {
206 - return cat( '', this.getUserInfo(), '@' ) + this.getHostPort();
207 - },
 163+ Uri.decode = function( s ) {
 164+ return decodeURIComponent( s ).replace( /\+/g, ' ' );
 165+ };
208166
209 - /**
210 - * Returns the query arguments of the URL, encoded into a string
211 - * Does not preserve the order of arguments passed into the URI. Does handle escaping.
212 - * @return {String}
213 - */
214 - getQueryString: function() {
215 - var args = [];
216 - $.each( this.query, function( key, val ) {
217 - var k = mw.Uri.encode( key );
218 - var vals = val === null ? [ null ] : $.makeArray( val );
219 - $.each( vals, function( i, v ) {
220 - args.push( k + ( v === null ? '' : '=' + mw.Uri.encode( v ) ) );
 167+ Uri.prototype = {
 168+
 169+ /**
 170+ * Parse a string and set our properties accordingly.
 171+ * @param {String} URI
 172+ * @param {Object} options
 173+ * @return {Boolean} success
 174+ */
 175+ _parse: function( str, options ) {
 176+ var matches = parser[ options.strictMode ? 'strict' : 'loose' ].exec( str );
 177+ var uri = this;
 178+ $.each( properties, function( i, property ) {
 179+ uri[ property ] = matches[ i+1 ];
221180 } );
222 - } );
223 - return args.join( '&' );
224 - },
225181
226 - /**
227 - * Returns everything after the authority section of the URI
228 - * @return {String}
229 - */
230 - getRelativePath: function() {
231 - return this.path + cat( '?', this.getQueryString(), '', true ) + cat( '#', this.fragment, '' );
232 - },
 182+ // uri.query starts out as the query string; we will parse it into key-val pairs then make
 183+ // that object the "query" property.
 184+ // we overwrite query in uri way to make cloning easier, it can use the same list of properties.
 185+ var q = {};
 186+ // using replace to iterate over a string
 187+ if ( uri.query ) {
 188+ uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ($0, $1, $2, $3) {
 189+ if ( $1 ) {
 190+ var k = Uri.decode( $1 );
 191+ var v = ( $2 === '' || $2 === undefined ) ? null : Uri.decode( $3 );
233192
234 - /**
235 - * Gets the entire URI string. May not be precisely the same as input due to order of query arguments.
236 - * @return {String} the URI string
237 - */
238 - toString: function() {
239 - return this.protocol + '://' + this.getAuthority() + this.getRelativePath();
240 - },
 193+ // If overrideKeys, always (re)set top level value.
 194+ // If not overrideKeys but this key wasn't set before, then we set it as well.
 195+ if ( options.overrideKeys || q[ k ] === undefined ) {
 196+ q[ k ] = v;
241197
242 - /**
243 - * Clone this URI
244 - * @return {Object} new URI object with same properties
245 - */
246 - clone: function() {
247 - return new mw.Uri( this );
248 - },
 198+ // Use arrays if overrideKeys is false and key was already seen before
 199+ } else {
 200+ // Once before, still a string, turn into an array
 201+ if ( typeof q[ k ] === 'string' ) {
 202+ q[ k ] = [ q[ k ] ];
 203+ }
 204+ // Add to the array
 205+ if ( $.isArray( q[ k ] ) ) {
 206+ q[ k ].push( v );
 207+ }
 208+ }
 209+ }
 210+ } );
 211+ }
 212+ this.query = q;
 213+ },
249214
250 - /**
251 - * Extend the query -- supply query parameters to override or add to ours
252 - * @param {Object} query parameters in key-val form to override or add
253 - * @return {Object} this URI object
254 - */
255 - extend: function( parameters ) {
256 - $.extend( this.query, parameters );
257 - return this;
258 - }
 215+ /**
 216+ * Returns user and password portion of a URI.
 217+ * @return {String}
 218+ */
 219+ getUserInfo: function() {
 220+ return cat( '', this.user, cat( ':', this.password, '' ) );
 221+ },
 222+
 223+ /**
 224+ * Gets host and port portion of a URI.
 225+ * @return {String}
 226+ */
 227+ getHostPort: function() {
 228+ return this.host + cat( ':', this.port, '' );
 229+ },
 230+
 231+ /**
 232+ * Returns the userInfo and host and port portion of the URI.
 233+ * In most real-world URLs, this is simply the hostname, but it is more general.
 234+ * @return {String}
 235+ */
 236+ getAuthority: function() {
 237+ return cat( '', this.getUserInfo(), '@' ) + this.getHostPort();
 238+ },
 239+
 240+ /**
 241+ * Returns the query arguments of the URL, encoded into a string
 242+ * Does not preserve the order of arguments passed into the URI. Does handle escaping.
 243+ * @return {String}
 244+ */
 245+ getQueryString: function() {
 246+ var args = [];
 247+ $.each( this.query, function( key, val ) {
 248+ var k = Uri.encode( key );
 249+ var vals = val === null ? [ null ] : $.makeArray( val );
 250+ $.each( vals, function( i, v ) {
 251+ args.push( k + ( v === null ? '' : '=' + Uri.encode( v ) ) );
 252+ } );
 253+ } );
 254+ return args.join( '&' );
 255+ },
 256+
 257+ /**
 258+ * Returns everything after the authority section of the URI
 259+ * @return {String}
 260+ */
 261+ getRelativePath: function() {
 262+ return this.path + cat( '?', this.getQueryString(), '', true ) + cat( '#', this.fragment, '' );
 263+ },
 264+
 265+ /**
 266+ * Gets the entire URI string. May not be precisely the same as input due to order of query arguments.
 267+ * @return {String} the URI string
 268+ */
 269+ toString: function() {
 270+ return this.protocol + '://' + this.getAuthority() + this.getRelativePath();
 271+ },
 272+
 273+ /**
 274+ * Clone this URI
 275+ * @return {Object} new URI object with same properties
 276+ */
 277+ clone: function() {
 278+ return new Uri( this );
 279+ },
 280+
 281+ /**
 282+ * Extend the query -- supply query parameters to override or add to ours
 283+ * @param {Object} query parameters in key-val form to override or add
 284+ * @return {Object} this URI object
 285+ */
 286+ extend: function( parameters ) {
 287+ $.extend( this.query, parameters );
 288+ return this;
 289+ }
 290+ };
 291+
 292+ var defaultProtocol = ( new Uri( documentLocation ) ).protocol;
 293+
 294+ return Uri;
259295 };
260296
261 -} )( jQuery );
 297+ // if we are running in a browser, inject the current document location, for relative URLs
 298+ if ( document && document.location && document.location.href ) {
 299+ mw.Uri = mw.UriRelative( document.location.href );
 300+ }
 301+
 302+} )( jQuery, mediaWiki );
Property changes on: branches/wmf/1.18wmf1/resources/mediawiki/mediawiki.Uri.js
___________________________________________________________________
Modified: svn:mergeinfo
262303 Merged /trunk/phase3/resources/mediawiki/mediawiki.Uri.js:r99444,99446,102616

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r99446ensure tests for mw.Uri work in non-browser environmentsneilk23:29, 10 October 2011
r99944Follow up to r99857;jeroendedauw10:17, 16 October 2011

Comments

#Comment by Jeroen De Dauw (talk | contribs)   22:26, 14 December 2011

r99944 is a typo I guess :)

Status & tagging log