csw-upload


The file upload CGI script. It is a preliminary version, probably has bugs and misfeatures. There are two files: csw-upload.scm which implements the functionality as a module, and the cgi-script itself, upload.

 ;;; 
 ;;; csw-upload.scm 
 ;;; 
 ;;;  Copyright (c) 2004 Grzegorz Chrupała 
 ;;;  This code is under the same license as Gauche itself 
  
 (define-module csw-upload 
   (use www.cgi) 
   (use text.html-lite) 
   (use sxml.tools) 
   (use text.tree) 
   (use gauche.parameter) 
   (use file.util) 
   (use srfi-13) 
   (use dbm) 
   (export   output-charset   upload-path   url-prefix 
    temp-path   stylesheet   cmd-upload   cmd-delete 
    cmd-list   cmd-search   cmd-save   upload-part-handlers) 
   ) 
 (select-module csw-upload) 
  
 (use dbm.gdbm) 
  
  
 ;; customization parameters 
 (define output-charset (make-parameter "UTF-8")) 
 (define upload-path    (make-parameter "/var/www/html/wiliki-upload")) 
 (define db-path        (make-parameter "/var/www/data/wiliki-upload")) 
 ;; we use a separate database, not wiliki's one 
 (define db-type        (make-parameter <gdbm>)) 
 (define url-prefix     (make-parameter "/wiliki-upload")) 
 (define temp-path      (make-parameter "/var/www/html/wiliki-upload/tmp")) 
 (define stylesheet     (make-parameter "/scheme.css")) 
 (define action (make-parameter "/cgi-bin/upload")) 
  
 (define *tmp-prefix* (build-path (temp-path) "wilikiupload")) 
 (define $$ values) ;; for now ignore gettext 
  
  
 ;;; Helper definitions 
 (define-syntax aif  
   (syntax-rules ()  
     ;; simple version  
     ((aif var expr iftrue iffalse)  
      (let ((var expr))  
        (if var  
            iftrue  
            iffalse)))  
     ;; version with a test procedure  
     ((aif var test expr iftrue iffalse)  
      (let ((var expr))  
        (if (test var)  
            iftrue  
            iffalse)))))  
  
 ;; ======= DB facilities : slightly adapted from wiliki.db =================== 
 ;; I don't simply use wiliki-with-db as we need to specify the value conversion 
 ;; procedure for the db. Alternatively wiliki/db.scm could be patched so 
 ;; wiliki-with-db accepts optional conversion procedures 
  
 ;; For this chunk of code: 
 ;; Copyright (c) 2003-2004 Shiro Kawai, All rights reserved. 
  
 ;; some constants 
 (define-constant *retry-limit* 3) 
 (define-constant *EAVAIL-message* "resource temporarily unavailable") 
  
 (define the-db (make-parameter #f)) 
  
  
 (define (db-try-open dbpath dbtype rwmode ) 
   ;; Try to open the database.  If it receives EAVAIL error, wait for 
   ;; one second and try again, up to *retry-limit* times. 
   (define (try retry mode) 
     (with-error-handler 
         (lambda (e) 
           (cond ((>= retry *retry-limit*) (raise e)) 
                 ((string-contains-ci (ref e 'message) *EAVAIL-message*) 
                  (sys-sleep 1) (try (+ retry 1) mode)) 
                 (else 
                  ;; we don't want to show the path of db to unknown 
                  ;; visitors 
                  (raise 
                   (make <error> :message #`"Couldn't open database file to ,|rwmode|."))))) 
       (lambda () 
         (dbm-open dbtype 
                   :path dbpath  
                   :rw-mode mode 
                   :value-convert `(,write-to-string ,read-from-string))))) 
  
   ;; If db file does not exist, we open it with :write mode, 
   ;; regardless of rwmode arg, so that the empty DB is created. 
   ;; Note that race condition will not happen here.  If there's no 
   ;; DB and two process simultaneously came to this code, only 
   ;; one can grab the write access of DB, and another will 
   ;; be kept waiting until the initial content is committed. 
   (try 0 (if (dbm-db-exists? dbtype dbpath) rwmode :write)) 
   ) 
  
 (define (check-db) 
   (or (the-db) 
       (error "WiLiKi uploads: database is not open"))) 
  
  
 (define (with-db thunk . opts) 
   (let-keywords* opts ((rwmode :read)) 
     (if (the-db) 
       (thunk) 
       (parameterize ((the-db (db-try-open (db-path) (db-type) rwmode))) 
         (dynamic-wind 
          (lambda () #f) 
          thunk 
          (lambda () 
            (unless (dbm-closed? (the-db)) 
              (dbm-close (the-db))))))))) 
  
 ;;;; ========================================================================= 
  
  
 (define-class <upload-file> () 
   ((name        :init-keyword :name)          ;; filename  
    (title       :init-keyword :title)         ;; name for display 
    (submitter   :init-keyword :submitter)     ;; person who contributed file 
    (description :init-keyword :description)))  ;; additional information 
  
 (define-reader-ctor (class-name <upload-file>) 
   (lambda (name title submitter description) 
     (make <upload-file> 
       :name        name 
       :title       title 
       :submitter   submitter 
       :description description))) 
  
 (define-method write-object ((self <upload-file>) (port <port>)) 
   (format port "#,(~s ~s ~s ~s ~s)" 
           (class-name <upload-file>) 
           (slot-ref self 'name) 
           (slot-ref self 'title) 
           (slot-ref self 'submitter) 
           (slot-ref self 'description))) 
  
       
 (define (html-page page pagename) 
   (define upload-url "?command=upload") 
   (define list-url   "?command=list") 
   `("Content-Style-Type: text/css\n" 
     ,(cgi-header 
       :content-type #`"text/html; charset=,(output-charset)") 
     ,(html-doctype :type :transitional) 
     ,(sxml:sxml->xml 
       `(html (head (title ,pagename) 
                   (link (@ (rel stylesheet) 
                            (type text/css) 
                            (url ,(stylesheet))))) 
              (body 
               (h1 ,pagename) 
               (div (@ (class upload-menu)) 
                    (a (@ (href ,upload-url)) ,($$ "Upload")) " | " 
                    (a (@ (href ,list-url))   ,($$ "List"))) 
                                 
              ,page))))) 
  
 (define (make-upload-file name param) 
   (make <upload-file> 
     :name          name  
     :title         (aif title string-null? 
                         (cgi-get-parameter "upload-file-title" param 
                                       :default "") 
                         name 
                         title) 
     :submitter     (aif submitter string-null? 
                         (cgi-get-parameter "upload-file-submitter"   param 
                                       :default "") 
                         (cgi-get-metavariable "REMOTE_ADDR") 
                         submitter) 
     :description   (cgi-get-parameter "upload-file-description" param 
                                            :default ""))) 
                          
  
 (define (cmd-upload param) 
   (html-page (upload-form) ($$ "CSW - File upload"))) 
  
  
 (define (cmd-delete param) 
   (let1 key (cgi-get-parameter "upload-file-name" param :default #f) 
     (unless key (error "No file for deletion specified")) 
     (with-db 
      (lambda () 
        (dbm-delete! (the-db) key) 
        (if (sys-unlink (build-path (upload-path) key)) 
            (html-page (list-files values) ($$ "CSW - File deleted")) 
            (html-page (list-files values) ($$ #`"CSW - File does not exists: ,key")))) 
        :rwmode :write))) 
      
  
  
 (define (cmd-save param) 
   (let ((source-name (cgi-get-parameter "upload-file-contents" 
                                         param 
                                         :default #f)) 
         (target-name (get-file-name (cgi-get-parameter "upload-file-name" 
                                                        param 
                                                        :default #f)))) 
     (unless source-name  
       (error "Source name of file to copy not given: " source)) 
     (unless target-name 
       (error "Target name of file to copy not given: " target)) 
     ;; create upload directory if it doesn't exist 
     (make-directory* (upload-path)) 
     (let1 upload-file (make-upload-file target-name param) 
       (save-file upload-file 
                  source-name 
                  (build-path (upload-path) target-name)) 
       (html-page (list-files values) 
                  ($$ "CSW - File saved"))))) 
  
 (define (cmd-list param) 
   (html-page (list-files values) 
              ($$ "CSW - Listing of uploaded files"))) 
  
 (define (cmd-search param) 
   (html-page (list-files (create-filter param)) 
                          ($$ "CSW - Search results"))) 
  
 (define (create-filter param) 
   (let ((name         (cgi-get-parameter "upload-file-name"        param :default "")) 
         (title        (cgi-get-parameter "upload-file-title"       param :default "")) 
         (submitter    (cgi-get-parameter "upload-file-submitter"   param :default "")) 
         (description  (cgi-get-parameter "upload-file-description" param :default ""))) 
     (lambda (fileinfo) 
       (and (string-contains-ci (slot-ref fileinfo 'name)        name) 
            (string-contains-ci (slot-ref fileinfo 'title)       title) 
            (string-contains-ci (slot-ref fileinfo 'submitter)   submitter) 
            (string-contains-ci (slot-ref fileinfo 'description) description))))) 
  
 (define (save-file upload-file source-name target-name) 
     ; this should alert the user when they are about to overwrite a file 
   (with-db 
    (lambda () 
      (dbm-put! (the-db) 
                (slot-ref upload-file 'name) upload-file) 
      (copy-file source-name target-name :if-exists :backup)) 
    :rwmode :write 
    )) 
                                                
 ;;better ways to sanitize user-provided file names welcome 
 (define (sanitize string) 
   (regexp-replace-all #/^\.+|[^a-zA-Z0-9 \-_\.]/ string "_")) 
  
 (define (get-file-name form-name) 
   (if (and (string? form-name) 
            (not (string-null? form-name))) 
       (sanitize form-name) 
       (sys-strftime "%Y-%m-%d-%T" (sys-gmtime (sys-time))))) 
  
 (define (upload-part-handlers) 
   `(("upload-file-contents" file ,*tmp-prefix*))) 
  
 (define (upload-form) 
   `((form 
      (@ (action ,(action)) (method POST) (enctype "multipart/form-data") 
         (id file-upload-form)) 
      (input (@ (type hidden) (name command) (value save))) 
      (div ,($$ "Choose file") 
           (br) 
           (input (@ (type file) (name upload-file-contents)))) 
      (div 
       ,($$ "Name (file will be saved under this name)") 
       (br) 
       (input (@ (type text) (name upload-file-name)))) 
      (div 
       ,($$ "Title") 
       (br) 
       (input (@ (type text) (name upload-file-title)))) 
      (div 
       ,($$ "Uploaded by") 
       (br) 
       (input (@ (type text) (name upload-file-submitter)))) 
      (div 
       ,($$ "Description") 
       (br) 
       (textarea (@ (class "upload-file-description") 
                    (name upload-file-description)) 
                 "")) 
      (input (@ (type submit)))))) 
  
 (define (list-files filterer) 
   `((h2 ,($$ "Search files")) 
     (div (@ (class upload-file-search-form)) 
          (form (@ (method "GET") (action ,(action)) (class file-upload-search)) 
                (input (@ (type hidden) (name command) (value search))) 
                (table (@ (class file-upload-search)) (tr 
                        (th ,($$ "Name: ")) 
                        (th ,($$ "Title:  ")) 
                        (th ,($$ "Uploaded by: ")) 
                        (th ,($$ "Description: "))) 
                       (tr 
                        (td 
                         (input (@ (type text) (name upload-file-name)))) 
                        (td 
                         (input (@ (type text) (name upload-file-title)))) 
                        (td 
                         (input (@ (type text) (name upload-file-submitter)))) 
                        (td 
                         (input  (@ (type text) (name upload-file-description)))))) 
                (input (@ (type submit) (value ,($$ "Search")))))) 
     (h2 ,($$ "Files found:")) 
     (ul (@ (class "file-list")) 
         ,@(map file-list-item (get-file-list filterer))))) 
  
 (define (get-file-list filterer) 
   (with-db 
    (lambda () 
      (sort (dbm-fold (the-db) 
                (lambda (key fileinfo seed) 
                  (if (filterer fileinfo) 
                      (cons fileinfo seed) 
                      seed)) '()) 
            (lambda (f1 f2) 
              (string-ci<? (slot-ref f1 'title) (slot-ref f2 'title)))) 
            ))) 
  
 (define (file-list-item fileinfo) 
   `(li (@ (class file-list-item) 
           (id ,(string-append "file-" (slot-ref fileinfo 'name)))) 
        (div (@ (class file-title)) 
             (a (@ (href ,(build-path (url-prefix) (slot-ref fileinfo 'name)))) 
                ,(slot-ref fileinfo 'title))) 
  
        (div (@ (class file-submitter)) ,(slot-ref fileinfo 'submitter)) 
        (div (@ (class file-description)) 
             ,(slot-ref fileinfo 'description)) 
        (form (@ (action ,(action)) (method "POST")) 
              (input (@ (type hidden) 
                        (name command) 
                        (value delete))) 
              (input (@ (type hidden) 
                        (name upload-file-name) 
                        (value ,(slot-ref fileinfo 'name)))) 
              (input (@ (type submit) 
                        (value ,($$ "Delete"))))))) 
  
 (provide "csw-upload") 
  

-----

 #!/usr/local/bin/gosh 
  
 ;;; 
 ;;; upload 
 ;;; 
 ;;;  Copyright (c) 2004 Grzegorz Chrupała 
 ;;;  This code is under the same license as Gauche itself 
  
 (add-load-path ".") 
 (use www.cgi) 
 (use csw-upload) 
  
 ;;; Customize parameters here 
  
 (cgi-main 
  (lambda (param) 
    (let1 command (string->symbol (cgi-get-parameter "command" param :default "")) 
      (case command 
        ((list)    (cmd-list   param)) 
        ((upload)  (cmd-upload param)) 
        ((save)    (cmd-save   param)) 
        ((search)  (cmd-search param)) 
        ((delete)  (cmd-delete param)) 
        (else      (cmd-list param))))) 
  :merge-cookies #t 
  :part-handlers (upload-part-handlers)) 
  

category-this-wiki