source: trunk/abcl/src/org/armedbear/lisp/URLPathname.java

Last change on this file was 15569, checked in by Mark Evenson, 2 years ago

Untabify en masse

Results of running style.org source blocks on tree

File size: 15.4 KB
Line 
1/*
2 * URLPathname.java
3 *
4 * Copyright (C) 2020 @easye
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 *
20 * As a special exception, the copyright holders of this library give you
21 * permission to link this library with independent modules to produce an
22 * executable, regardless of the license terms of these independent
23 * modules, and to copy and distribute the resulting executable under
24 * terms of your choice, provided that you also meet, for each linked
25 * independent module, the terms and conditions of the license of that
26 * module.  An independent module is a module which is not derived from
27 * or based on this library.  If you modify this library, you may extend
28 * this exception to your version of the library, but you are not
29 * obligated to do so.  If you do not wish to do so, delete this
30 * exception statement from your version.
31 */
32
33package org.armedbear.lisp;
34
35import static org.armedbear.lisp.Lisp.*;
36
37import java.io.File;
38import java.io.InputStream;
39import java.io.IOException;
40import java.net.URL;
41import java.net.URLConnection;
42import java.net.URI;
43import java.net.MalformedURLException;
44import java.net.URISyntaxException;
45import java.text.MessageFormat;
46
47public class URLPathname
48  extends Pathname
49{
50  static public final Symbol SCHEME = internKeyword("SCHEME");
51  static public final Symbol AUTHORITY = internKeyword("AUTHORITY");
52  static public final Symbol QUERY = internKeyword("QUERY");
53  static public final Symbol FRAGMENT = internKeyword("FRAGMENT");
54
55  protected URLPathname() {}
56
57  public static URLPathname create() {
58    return new URLPathname();
59  }
60
61  public static URLPathname create(Pathname p) {
62    if (p instanceof URLPathname) {
63      URLPathname result = new URLPathname();
64      result.copyFrom(p);
65      return result;
66    }
67    return (URLPathname)createFromFile((Pathname)p);
68  }
69
70  public static URLPathname create(URL url) {
71    return URLPathname.create(url.toString());
72  }
73
74  public static URLPathname create(URI uri) {
75    return URLPathname.create(uri.toString());
76  }
77
78  static public final LispObject FILE = new SimpleString("file");
79  public static URLPathname createFromFile(Pathname p) {
80    URLPathname result = new URLPathname();
81    result.copyFrom(p);
82    LispObject scheme = NIL;
83    scheme = scheme.push(FILE).push(SCHEME);
84    result.setHost(scheme);
85    return result;
86  }
87
88  public static URLPathname create(String s) {
89    if (!isValidURL(s)) {
90      parse_error("Cannot form a PATHNAME-URL from " + s);
91    }
92    if (s.startsWith(JarPathname.JAR_URI_PREFIX)) {
93      return JarPathname.create(s);
94    }
95
96    URLPathname result = new URLPathname();
97    URL url = null;
98    try {
99      url = new URL(s);
100    } catch (MalformedURLException e) {
101      parse_error("Malformed URL in namestring '" + s + "': " + e.toString());
102      return (URLPathname) UNREACHED;
103    }
104    String scheme = url.getProtocol();
105    if (scheme.equals("file")) {
106      URI uri = null;
107      try {
108        uri = new URI(s);
109      } catch (URISyntaxException ex) {
110        parse_error("Improper URI syntax for "
111                    + "'" + url.toString() + "'"
112                    + ": " + ex.toString());
113        return (URLPathname)UNREACHED;
114      }
115           
116      String uriPath = uri.getPath();
117      if (null == uriPath) {
118        // Under Windows, deal with pathnames containing
119        // devices expressed as "file:z:/foo/path"
120        uriPath = uri.getSchemeSpecificPart();
121        if (uriPath == null || uriPath.equals("")) {
122          parse_error("The namestring URI has no path: " + uri);
123          return (URLPathname)UNREACHED;
124        }
125      }
126      final File file = new File(uriPath);
127      String path = file.getPath();
128      if (uri.toString().endsWith("/") && !path.endsWith("/")) {
129        path += "/";
130      }
131      final Pathname p = (Pathname)Pathname.create(path);
132      LispObject host = NIL.push(FILE).push(SCHEME);
133      result
134        .setHost(host)
135        .setDevice(p.getDevice())
136        .setDirectory(p.getDirectory())
137        .setName(p.getName())
138        .setType(p.getType())
139        .setVersion(p.getVersion());
140      return result; 
141    }
142    Debug.assertTrue(scheme != null);
143    URI uri = null;
144    try { 
145      uri = url.toURI().normalize();
146    } catch (URISyntaxException e) {
147      parse_error("Couldn't form URI from "
148                  + "'" + url + "'"
149                  + " because: " + e);
150      return (URLPathname)UNREACHED;
151    }
152    String authority = uri.getAuthority();
153    if (authority == null) {
154      authority = url.getAuthority();
155    }
156
157    LispObject host = NIL;
158    host = host.push(SCHEME).push(new SimpleString(scheme));
159    if (authority != null) {
160      host = host.push(AUTHORITY).push(new SimpleString(authority));
161    }
162    String query = uri.getRawQuery();
163    if (query != null) {
164      host = host.push(QUERY).push(new SimpleString(query));
165    }
166    String fragment = uri.getRawFragment();
167    if (fragment != null) {
168      host = host.push(FRAGMENT).push(new SimpleString(fragment));
169    }
170    host = host.nreverse();
171    result.setHost(host);
172
173    // URI encode necessary characters
174    String path = uri.getRawPath();
175    if (path == null) {
176      path = "";
177    } 
178
179    Pathname p = (Pathname)Pathname.create(path != null ? path : ""); 
180    result
181      .setDirectory(p.getDirectory())
182      .setName(p.getName())
183      .setType(p.getType());
184
185    return result;
186  }
187
188  public URI toURI() {
189    String uriString = getNamestringAsURL();
190    try {
191      URI uri = new URI(uriString);
192      return uri;
193    } catch (URISyntaxException eo) {
194      return null;
195    }
196  }
197
198  public URL toURL() {
199    URI uri = toURI();
200    try {
201      if (uri != null) {
202        return uri.toURL();
203      }
204    } catch (MalformedURLException e) { 
205    }
206    return null;
207  }
208 
209  public File getFile() { 
210    if (!hasExplicitFile(this)) {
211      return null; // TODO signal that this is not possible?
212    }
213    URI uri = toURI();
214    if (uri == null) {
215      return null;
216    }
217    File result = new File(uri);
218    return result;
219  }
220
221  static public boolean isFile(Pathname p) {
222    LispObject scheme = Symbol.GETF.execute(p.getHost(), SCHEME, NIL);
223    if (scheme.equals(NIL)
224        || hasExplicitFile(p)) {
225      return true;
226    }
227    return false;
228  }
229 
230  static public boolean hasExplicitFile(Pathname p) {
231    if (!p.getHost().listp()) {
232        return false;
233    }
234    LispObject scheme = Symbol.GETF.execute(p.getHost(), SCHEME, NIL);
235    return scheme.equalp(FILE);
236  }
237
238  public String getNamestring() {
239    StringBuilder sb = new StringBuilder();
240    return getNamestring(sb);
241  }
242 
243  public String getNamestring(StringBuilder sb) {
244    LispObject scheme = Symbol.GETF.execute(getHost(), SCHEME, NIL);
245    LispObject authority = Symbol.GETF.execute(getHost(), AUTHORITY, NIL);
246
247    // A scheme of NIL is implicitly "file:", for which we don't emit
248    // as part of the usual namestring.  getNamestringAsURI() should
249    // emit the 'file:' string
250    boolean percentEncode = true;
251    if (scheme.equals(NIL)) {
252      percentEncode = false;
253    } else {
254      sb.append(scheme.getStringValue());
255      sb.append(":");
256      if (authority != NIL) {
257        sb.append("//");
258        sb.append(authority.getStringValue());
259      } else if (scheme.equalp(FILE)) {
260        sb.append("//");
261      }
262    }
263    // <https://docs.microsoft.com/en-us/archive/blogs/ie/file-uris-in-windows>
264    if (Utilities.isPlatformWindows
265        && getDevice() instanceof SimpleString) {
266      sb.append("/")
267        .append(getDevice().getStringValue())
268        .append(":");
269    }
270    String directoryNamestring = getDirectoryNamestring();
271    if (percentEncode) {
272      directoryNamestring = uriEncode(directoryNamestring);
273    }
274    sb.append(directoryNamestring);
275
276    // Use the output of Pathname
277    Pathname p = new Pathname();
278    p.copyFrom(this)
279      .setHost(NIL)
280      .setDevice(NIL)
281      .setDirectory(NIL);
282    String nameTypeVersion = p.getNamestring();
283    if (percentEncode) {
284      nameTypeVersion = uriEncode(nameTypeVersion);
285    }     
286    sb.append(nameTypeVersion);
287
288    LispObject o = Symbol.GETF.execute(getHost(), QUERY, NIL);
289    if (o != NIL) {
290      sb.append("?")
291        .append(uriEncode(o.getStringValue()));
292    }
293    o = Symbol.GETF.execute(getHost(), FRAGMENT, NIL);
294    if (o != NIL) {
295      sb.append("#")
296        .append(uriEncode(o.getStringValue()));
297    }
298
299    return sb.toString();
300  }
301
302  // We need our "own" rules for outputting a URL
303  // 1.  For DOS drive letters
304  // 2.  For relative "file" schemas (??)
305  public String getNamestringAsURL() {
306    LispObject schemeProperty = Symbol.GETF.execute(getHost(), SCHEME, NIL);
307    LispObject authorityProperty = Symbol.GETF.execute(getHost(), AUTHORITY, NIL);
308    LispObject queryProperty = Symbol.GETF.execute(getHost(), QUERY, NIL);
309    LispObject fragmentProperty = Symbol.GETF.execute(getHost(), FRAGMENT, NIL);
310
311    String scheme;
312    String authority = null;
313    if (!schemeProperty.equals(NIL)) {
314      scheme = schemeProperty.getStringValue();
315      if (!authorityProperty.equals(NIL)) {
316        authority =  authorityProperty.getStringValue();
317      }
318    } else {
319      scheme = "file";
320    }
321
322    String directory = getDirectoryNamestring();
323    String file = "";
324    LispObject fileNamestring = Symbol.FILE_NAMESTRING.execute(this);
325    if (!fileNamestring.equals(NIL)) {
326      file = fileNamestring.getStringValue();
327    }
328    String path = "";
329
330    if (!directory.equals("")) {
331      if (Utilities.isPlatformWindows
332          && getDevice() instanceof SimpleString) {
333        path = getDevice().getStringValue() + ":" + directory + file;
334      } else {
335        path = directory + file;
336      }
337    } else {
338      path = file;
339    }
340
341    path = uriEncode(path);
342
343    String query = null;
344    if (!queryProperty.equals(NIL)) {
345      query = queryProperty.getStringValue();
346    }
347
348    String fragment = null;
349    if (!fragmentProperty.equals(NIL)) {
350      fragment = fragmentProperty.getStringValue();
351    }
352
353    StringBuffer result = new StringBuffer(scheme);
354    result.append(":");
355    result.append("//");
356    if (authority != null) {
357      result.append(authority);
358    }
359    if (!path.startsWith("/")) {
360      result.append("/");
361    }
362    result.append(path);
363
364    if (query != null) {
365      result.append("?").append(query);
366    }
367
368    if (fragment != null) {
369      result.append("#").append(fragment);
370    }
371    return result.toString();
372  }
373
374  public LispObject typeOf() {
375    return Symbol.URL_PATHNAME;
376  }
377
378  @Override
379  public LispObject classOf() {
380    return BuiltInClass.URL_PATHNAME;
381  }
382
383  public static LispObject truename(Pathname p, boolean errorIfDoesNotExist) {
384    URLPathname pathnameURL = (URLPathname)URLPathname.createFromFile(p);
385    return URLPathname.truename(pathnameURL, errorIfDoesNotExist);
386  }
387
388  public static LispObject truename(URLPathname p, boolean errorIfDoesNotExist) {
389    if (p.getHost().equals(NIL)
390        || hasExplicitFile(p)) {
391      LispObject fileTruename = Pathname.truename(p, errorIfDoesNotExist);
392      if (fileTruename.equals(NIL)) {
393        return NIL;
394      }
395      if (!(fileTruename instanceof URLPathname)) {
396        URLPathname urlTruename = URLPathname.createFromFile((Pathname)fileTruename);
397        return urlTruename;
398      }
399      return fileTruename;
400    }
401       
402    if (p.getInputStream() != null) {
403      // If there is no type, query or fragment, we check to
404      // see if there is URL available "underneath".
405      if (p.getName() != NIL
406          && p.getType() == NIL
407          && Symbol.GETF.execute(p.getHost(), URLPathname.QUERY, NIL) == NIL
408          && Symbol.GETF.execute(p.getHost(), URLPathname.FRAGMENT, NIL) == NIL) {
409        if (p.getInputStream() != null) {
410          return p;
411        }
412      }
413      return p;
414    }
415    return Pathname.doTruenameExit(p, errorIfDoesNotExist);
416  }
417 
418  public InputStream getInputStream() {
419    InputStream result = null;
420
421    if (URLPathname.isFile(this)) {
422      Pathname p = new Pathname();
423      p.copyFrom(this)
424        .setHost(NIL);
425      return p.getInputStream();
426    }
427
428    if (URLPathname.isFile(this)) {
429      Pathname p = new Pathname();
430      p.copyFrom(this)
431        .setHost(NIL);
432      return p.getInputStream();
433    }
434
435    URL url = this.toURL();
436    try { 
437      result = url.openStream();
438    } catch (IOException e) {
439      Debug.warn("Failed to get InputStream from "
440                 + "'" + getNamestring() + "'"
441                 + ": " + e);
442    }
443    return result;
444  }
445
446  URLConnection getURLConnection() {
447    Debug.assertTrue(isURL());
448    URL url = this.toURL();
449    URLConnection result = null;
450    try {
451      result = url.openConnection();
452    } catch (IOException e) {
453      error(new FileError("Failed to open URL connection.",
454                          this));
455    }
456    return result;
457  }
458
459  public long getLastModified() {
460    return getURLConnection().getLastModified();
461  }
462
463  @DocString(name="uri-decode",
464             args="string",
465             returns="string",
466             doc="Decode STRING percent escape sequences in the manner of URI encodings.")
467  private static final Primitive URI_DECODE = new pf_uri_decode();
468  private static final class pf_uri_decode extends Primitive {
469    pf_uri_decode() {
470      super("uri-decode", PACKAGE_EXT, true);
471    }
472    @Override
473    public LispObject execute(LispObject arg) {
474      if (!(arg instanceof AbstractString)) {
475        return type_error(arg, Symbol.STRING);
476      }
477      String result = uriDecode(((AbstractString)arg).toString());
478      return new SimpleString(result);
479    }
480  };
481
482  static String uriDecode(String s) {
483    try {
484      URI uri = new URI("file://foo?" + s);
485      return uri.getQuery();
486    } catch (URISyntaxException e) {}
487    return null;  // Error
488  }
489   
490  @DocString(name="uri-encode",
491             args="string",
492             returns="string",
493             doc="Encode percent escape sequences in the manner of URI encodings.")
494  private static final Primitive URI_ENCODE = new pf_uri_encode();
495  private static final class pf_uri_encode extends Primitive {
496    pf_uri_encode() {
497      super("uri-encode", PACKAGE_EXT, true);
498    }
499    @Override
500    public LispObject execute(LispObject arg) {
501      if (!(arg instanceof AbstractString)) {
502        return type_error(arg, Symbol.STRING);
503      }
504      String result = uriEncode(((AbstractString)arg).toString());
505      return new SimpleString(result);
506    }
507  };
508
509  static String uriEncode(String s) {
510    // The constructor we use here only allows absolute paths, so
511    // we manipulate the input and output correspondingly.
512    String u;
513    if (!s.startsWith("/")) {
514      u = "/" + s;
515    } else {
516      u = new String(s);
517    }
518    try {
519      URI uri = new URI("file", "", u, "");
520      String result = uri.getRawPath();
521      if (!s.startsWith("/")) {
522        return result.substring(1);
523      } 
524      return result;
525    } catch (URISyntaxException e) {
526      Debug.assertTrue(false);
527    }
528    return null; // Error
529  }
530}
Note: See TracBrowser for help on using the repository browser.