1  /**
2  *  SafeFN is a Javascript implementation of Christopher Smith's php
3  *  SafeFN class which was written for Dokuwiki
4  *
5  *  @author Myron Turner <turnermm@shaw.ca>
6  *  @copyright Myron Turner (C) GPL 2 or greater
7  */
8
9var SafeFN = {
10    plain: '-./[_0123456789abcdefghijklmnopqrstuvwxyz', // these characters aren't converted
11	pre_indicator: '%',
12	post_indicator:']',
13
14    /**
15     * convert numbers from base 10 to base 36 and base 36 to base 10
16     *
17     * @param  string representing an integer or integer   num  number to be converted
18     * @param  integer      from    base from which to convert
19     * @param  integer      to      base to which to convert
20     *
21     * @return  array   int    an array of unicode codepoints
22     *
23	 * @author   Myron Turner <turnermm02@shaw.ca>
24     */
25
26	 changeSafeBase: function(num, from, to)   {
27	      if(isNaN(from) || from < 2 || from > 36 || isNaN(to) || to < 2 || to > 36) {
28	        throw (new RangeError("Illegal radix. Radices must be integers between 2 and 36, inclusive."));
29	      }
30	      num = parseInt(num, from);
31	      if(from == 36) return num;
32	      return num.toString(to);
33	  },
34
35
36    /**
37     * convert a UTF8 string into an array of unicode code points
38     *
39     * @param   UTF8 string
40     * @return  array   int    an array of unicode codepoints
41     *
42	 * @author   Myron Turner <turnermm02@shaw.ca>
43     */
44
45	get_u_array: function(utf8str) {
46	    var unicode_array = new Array();
47		for (var i=0; i<utf8str.length; i++) {
48	           unicode_array[i] = utf8str.charCodeAt(i);;
49		}
50	   return unicode_array;
51	},
52
53
54    /**
55     * convert a 'safe_filename' string into an array of unicode codepoints
56     *
57     * @param   string         safe     a filename in 'safe_filename' format
58     * @return  array   int    an array of unicode codepoints
59  	 * @author   Christopher Smith <chris@jalakai.co.uk>
60     * @author   Myron Turner<turnermm02@shaw.ca>
61     */
62	safe_to_unicode: function(safe) {
63    	var unicode = new Array();
64    	var regex = new RegExp('(?=[' + this.pre_indicator + '\\' + this.post_indicator + '])');
65    	var split_array = safe.split(regex);
66    	var converted = false;
67
68    	for (var i = 0; i<split_array.length; i++ ) {
69    	    var sub = split_array[i];
70    	    if (sub.charAt(0) != this.pre_indicator) { //  i.e. sub.charAt(0) != '%'
71    	        var start = converted?1:0;
72    	        for (j=start; j < sub.length; j++) {
73    	             unicode.push(sub.charCodeAt(j));
74    	        }
75    	        converted = false;
76    	    } else if (sub.length==1) {
77    	        unicode.push(sub.charCodeAt(0));
78    	        converted = true;
79    	    } else {
80
81    	        unicode.push(32 +  this.changeSafeBase(sub.slice(1),36,10));
82    	        converted = true;
83    	    }
84    	}
85
86    	return unicode;
87	},
88
89	/**
90	* convert an array of unicode codepoints into 'safe_filename' format
91	*
92	* @param    array  int    $unicode    an array of unicode codepoints
93	* @return   string        the unicode represented in 'safe_filename' format
94	*
95	* @author   Christopher Smith <chris@jalakai.co.uk>
96	* @author   Myron Turner <turnermm02@shaw.ca>
97	*/
98	unicode_to_safe: function (unicode) {
99    	var safe = '';
100    	var converted = false;
101    	var plain_str = this.plain + this.post_indicator;
102
103    	for (var i=0; i< unicode.length; i++) {
104    	     codepoint = unicode[i];
105             var match = '';
106             if(String.fromCharCode(codepoint) != '\\') {
107               var regex = new RegExp(String.fromCharCode(codepoint));
108               var match = plain_str.match(regex);
109             }
110
111             if (codepoint < 127 && match) {
112    	        if (converted) {
113    	            safe += this.post_indicator;
114    	            converted = false;
115    	        }
116    	        safe += String.fromCharCode(codepoint);
117
118    	    } else if (codepoint == this.pre_indicator.charCodeAt(0)) {
119    	        safe += this.pre_indicator;
120    	        converted = true;
121    	    } else {
122    	        safe += this.pre_indicator + this.changeSafeBase((codepoint-32), 10, 36);
123    	        converted = true;
124    	    }
125    	}
126        if(converted) safe += this.post_indicator;
127
128    	return safe;
129	},
130
131  /**
132     * Convert an UTF-8 string to a safe ASCII String
133     *
134     *
135     * @param    string    filename     a utf8 string, should only include printable characters - not 0x00-0x1f
136     * @return   string    an encoded representation of filename using only 'safe' ASCII characters
137     *
138  	 * @author   Myron Turner <turnermm02@shaw.ca>
139     */
140	encode: function(filename) {
141    	return this.unicode_to_safe(this.get_u_array(filename));
142	},
143
144    /**
145     * decode a 'safe' encoded file name and return a UTF8 string
146     *
147     * @param    string    filename     a 'safe' encoded ASCII string,
148     * @return   string    decoded utf8 string
149     *
150  	 * @author   Myron Turner <turnermm02@shaw.ca>
151     */
152
153	decode: function (filename) {
154		var unic = this.safe_to_unicode(filename);
155
156	    // convert unicode code points to utf8
157		var str = new Array();
158		for (var i=0; i < unic.length; i++) {
159			str[i] = this.code2utf(unic[i]);
160		}
161        // return the decoded string
162		return this.utf8Decode(str.join(''));
163	},
164
165/* UTF8 encoding/decoding functions
166 * Copyright (c) 2006 by Ali Farhadi.
167 * released under the terms of the Gnu Public License.
168 * see the GPL for details.
169 *
170 * Email: ali[at]farhadi[dot]ir
171 * Website: http://farhadi.ir/
172 */
173
174//an alias of String.fromCharCode
175chr: function (code)
176{
177	return String.fromCharCode(code);
178},
179
180//returns utf8 encoded charachter of a unicode value.
181//code must be a number indicating the Unicode value.
182//returned value is a string between 1 and 4 charachters.
183code2utf: function (code)
184{
185	if (code < 128) return this.chr(code);
186	if (code < 2048) return this.chr(192+(code>>6)) + this.chr(128+(code&63));
187	if (code < 65536) return this.chr(224+(code>>12)) + this.chr(128+((code>>6)&63)) + this.chr(128+(code&63));
188	if (code < 2097152) return this.chr(240+(code>>18)) + this.chr(128+((code>>12)&63)) + this.chr(128+((code>>6)&63)) + this.chr(128+(code&63));
189},
190
191//it is a private function for internal use in utf8Decode function
192_utf8Decode: function (utf8str)
193{
194
195	var str = new Array();
196	var code,code2,code3,code4,j = 0;
197	for (var i=0; i<utf8str.length; ) {
198		code = utf8str.charCodeAt(i++);
199
200
201		if (code > 127) code2 = utf8str.charCodeAt(i++);
202		if (code > 223) code3 = utf8str.charCodeAt(i++);
203		if (code > 239) code4 = utf8str.charCodeAt(i++);
204
205		if (code < 128) str[j++]= this.chr(code);
206		else if (code < 224) str[j++] = this.chr(((code-192)<<6) + (code2-128));
207		else if (code < 240) str[j++] = this.chr(((code-224)<<12) + ((code2-128)<<6) + (code3-128));
208		else str[j++] = this.chr(((code-240)<<18) + ((code2-128)<<12) + ((code3-128)<<6) + (code4-128));
209
210	}
211	return str.join('');
212},
213
214//Decodes a UTF8 formated string
215utf8Decode: function (utf8str)
216{
217	var str = new Array();
218	var pos = 0;
219	var tmpStr = '';
220	var j=0;
221	while ((pos = utf8str.search(/[^\x00-\x7F]/)) != -1) {
222		tmpStr = utf8str.match(/([^\x00-\x7F]+[\x00-\x7F]{0,10})+/)[0];
223		str[j++]= utf8str.substr(0, pos) + this._utf8Decode(tmpStr);
224		utf8str = utf8str.substr(pos + tmpStr.length);
225	}
226
227	str[j++] = utf8str;
228	return str.join('');
229}
230
231};
232
233function SafeFN_encode(filename) {
234  return SafeFN.encode(filename);
235}
236
237function SafeFN_decode(filename) {
238    return SafeFN.decode(filename);
239}
240
241
242