1
0
Fork 0
tinmop/src/uri-parser.lisp

331 lines
9.7 KiB
Common Lisp

;; tinmop: a multiprotocol client
;; Copyright © cage
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program.
;; If not, see [[http://www.gnu.org/licenses/][http://www.gnu.org/licenses/]].
(in-package :uri-parser)
;; NOTE: the parser is broken, use :iri-parser, instead
(define-constant +segment-separator+ "/" :test #'string=)
(defrule alpha (character-ranges (#\a #\z) (#\A #\Z))
(:text t))
(defrule digit (character-ranges (#\0 #\9))
(:text t))
(defrule scheme-delim #\:
(:constant :scheme-delim))
(defrule query-delim #\?
(:constant :query-delim))
(defrule fragment-delim #\#
(:constant :fragment-delim))
(defrule port-delim #\:
(:constant :port-delim))
(defrule credential-delim #\@
(:constant :credential-delim))
(defrule authority-start "//"
(:constant :authority-start))
(defrule sub-delims (or #\! #\$ #\& #\' #\( #\) #\* #\+ #\, #\; #\=)
(:text t))
(defrule gen-delims (or ":" "?" "#" "[" "]" "@" "")
(:text t))
(defrule unreserved-chars (or alpha digit #\- #\. #\_ #\~)
(:text t))
(defrule reserved-chars (or gen-delims sub-delims)
(:text t))
(defrule scheme (and alpha (* (or alpha digit "+" "-" "." )))
(:text t))
(defrule hier-part (and authority-start authority)
(:function second))
(defrule user-credentials (and userinfo credential-delim)
(:function first))
(defrule port-block (and port-delim port)
(:function second)
(:function parse-integer))
(defrule authority (and (? user-credentials)
host
(? port-block)))
(defrule reg-name (* (or unreserved-chars pct-encoded sub-delims ))
(:text t))
(defrule host (or ipv4-address ip-literal reg-name)
(:text t))
(defrule port (+ digit)
(:text t))
(defrule userinfo (* (or unreserved-chars pct-encoded sub-delims ":" ))
(:text t))
(defrule pct-encoded (and "%" hexdig hexdig)
(:text t))
(defrule hexdig (or (character-ranges #\a #\f) digit)
(:text t))
(defrule ipv4-address (and dec-octet "." dec-octet "." dec-octet "." dec-octet)
(:text t))
(defrule ip-literal (and "["
(+ (not (or "[" "]")))
"]")
(:text t))
(defrule pchar (or unreserved-chars pct-encoded sub-delims ":" "@")
(:text t))
(defrule segment (* pchar)
(:text t))
(defrule segment-non-zero (+ pchar)
(:text t))
(defrule segment-nz-nc (+ (or unreserved-chars pct-encoded sub-delims "@" ))
(:text t))
(defrule path-abempty (* (and "/" segment))
(:text t))
(defrule path (or path-abempty
path-absolute
path-noscheme
path-rootless
path-empty)
(:text t))
(defrule path-absolute (and "/" (? (and segment-nz (* (and "/" segment )))))
(:text t))
(defrule path-rootless (and segment-non-zero (* (and "/" segment )))
(:text t))
(defrule path-noscheme (and segment-nz-nc (* (and "/" segment )))
(:text t))
(defrule path-empty ""
(:constant nil))
(defun octect-p (maybe-octect)
(ignore-errors
(let ((number (parse-integer (text-utils:strcat* maybe-octect))))
(when (<= 0 number 255)
number))))
(defrule dec-octet (octect-p (+ digit))
(:text t))
(defun extract-fields-from-absolute-uri (parsed)
(let ((authority (third parsed)))
(list (first parsed) ; scheme
(first authority) ; user-credentials
(second authority) ; host
(third authority) ; port
(fourth parsed) ; path
(fifth parsed) ; query
(sixth parsed)))) ; fragment
(defrule uri (and scheme ":"
hier-part
(or path-abempty
path-absolute
path-noscheme
path-empty)
(? query)
(? fragment))
(:function extract-fields-from-absolute-uri))
(defrule relative-part (or (and authority-start
authority
path-abempty)
path-absolute
path-noscheme
path-empty))
(defun extract-fields-from-relative-uri-w-authority (parsed)
;; ((:IAUTHORITY-START (NIL "bar.baz" NIL) "/foo.gmi") "a=b" "afrag")
(let ((authority (second (first parsed)))
(path (third (first parsed))))
(list nil ; scheme
(first authority) ; user-credentials
(second authority) ; host
(third authority) ; port
path
(second parsed) ; iquery
(third parsed)))) ; fragment
(defun extract-fields-from-relative-uri-w/o-authority (parsed)
(list nil ; scheme
nil ; user-credentials
nil ; host
nil ; port
(first parsed) ; path
(second parsed) ; iquery
(third parsed))) ; fragment
(defun extract-fields-from-relative-uri (parsed)
(if (consp (first parsed))
(extract-fields-from-relative-uri-w-authority parsed)
(extract-fields-from-relative-uri-w/o-authority parsed)))
(defrule relative-ref (and relative-part (? query) (? fragment))
(:function extract-fields-from-relative-uri))
(defrule query (and query-delim (* (or pchar "/" "?")))
(:function second)
(:text t))
(defrule fragment (and fragment-delim (* (or pchar "/" "?")))
(:function second)
(:text t))
(defrule uri-reference (or uri relative-ref))
(defclass uri ()
((scheme
:initform nil
:initarg :scheme
:accessor scheme)
(user-info
:initform nil
:initarg :user-info
:accessor user-info)
(host
:initform nil
:initarg :host
:writer (setf host))
(port
:initform nil
:initarg :port
:accessor port)
(path
:initform nil
:initarg :path
:accessor path)
(query
:initform nil
:initarg :query
:accessor query)
(fragment
:initform nil
:initarg :fragment
:accessor fragment)))
(defgeneric host (object))
(defmethod host ((object uri))
(let ((host (slot-value object 'host)))
(if (text-utils:string-starts-with-p "[" host)
(subseq host 1 (1- (length host)))
host)))
(defun make-uri (&optional scheme user-info host port path query fragment)
(make-instance 'uri
:scheme scheme
:user-info user-info
:host host
:port port
:path path
:query query
:fragment fragment))
(defun uri-parse (uri)
(let* ((parsed (parse 'uri-reference uri :junk-allowed nil))
(res (mapcar (lambda (a) (cond
((typep a 'string)
(if (text-utils:string-empty-p a)
nil
a))
(t a)))
(list (first parsed) ; scheme
(second parsed) ; user-credentials
(third parsed) ; host
(fourth parsed) ; port
(fifth parsed) ; path
(sixth parsed) ; query
(seventh parsed))))) ; fragment
(values (apply #'make-uri res)
res)))
(defun copy-uri (from)
(let ((scheme (scheme from))
(user-info (user-info from))
(host (slot-value from 'host))
(port (port from))
(path (path from))
(query (query from))
(fragment (fragment from)))
(make-uri scheme
user-info
host
port
path
query
fragment)))
(defun render-uri (uri &optional (stream *standard-output*))
(flet ((render ()
(with-output-to-string (string-stream)
(let ((scheme (scheme uri))
(user-info (user-info uri))
(host (slot-value uri 'host))
(port (port uri))
(path (path uri))
(query (query uri))
(fragment (fragment uri)))
(when scheme
(format string-stream "~a:" scheme))
(write-string "//" string-stream)
(when user-info
(format string-stream "~a@" user-info))
(when host
(format string-stream "~a" host))
(when port
(format string-stream ":~a" port))
(when path
(format string-stream "~a" path))
(when query
(format string-stream "?~a" query))
(when fragment
(format string-stream "#~a" fragment))))))
(write-string (render) stream)))
(defmethod normalize-path ((object uri:uri))
(let ((clean-path (normalize-path (uri:path object)))
(copy (uri:copy-uri object)))
(when clean-path
(setf (uri:path copy) clean-path))
copy))
(defmethod to-s ((object uri:uri) &key &allow-other-keys)
(with-output-to-string (stream)
(uri:render-uri object stream)))