BR 2005-06-02 - I also tried to make it work. I wanted to test a Java upload servlet which was based on Jakarta's commons-fileupload.Problems:
- The mime package doesn't have a getheaders method and getbody doesn't work for multipart. I think buildmessage should be split into those two functions. That is the "headers-problem" mentioned above.
- The mime package appends a spurious additional \r\n to each item in the multipart data. That is because mime appends a \r\n to each item and than also adds a \r\n before the boundary.
- The http package can't source POST data from memory, it wants a channel (or alternatively, the mime package can't produce a channel for use with http).
- Commons-fileupload doesn't understand the quoted boundary spec correctly which the mime package produces in the Content-Type header. This is a bug in commons-fileupload, I guess, but maybe the mime package should not make things complicated here. Update 2006-06-06 (just for the record): Commons-fileupload has this fixed in the CVS.
# Provide multipart/form-data for http
package provide form-data 1.0
package require mime
namespace eval form-data {}
proc form-data::compose {partv {type multipart/form-data}} {
upvar 1 $partv parts
set mime [mime::initialize -canonical $type -parts $parts]
set packaged [mime::buildmessage $mime]
foreach part $parts {
mime::finalize $part
}
mime::finalize $mime
return $packaged
}
proc form-data::add_binary {partv name filename value type} {
upvar 1 $partv parts
set disposition "form-data; name=\"${name}\"; filename=\"$filename\""
lappend parts [mime::initialize -canonical $type \
-string $value \
-encoding binary \
-header [list Content-Disposition $disposition]]
}
proc form-data::add_field {partv name value} {
upvar 1 $partv parts
set disposition "form-data; name=\"${name}\""
lappend parts [mime::initialize -canonical text/plain -string $value \
-header [list Content-Disposition $disposition]]
}
proc form-data::format {name filename value type args} {
set parts {}
foreach {n v} $args {
add_field parts $n $v
}
add_binary parts $name $filename $value $type
return [compose parts]
}
if {[info script] eq $argv0} {
# format a gif file upload according to the following form:
#<FORM METHOD="POST" ENCTYPE="multipart/form-data" ACTION="upload.php">
#<INPUT TYPE="HIDDEN" NAME="MAX_FILE_SIZE" VALUE=" ">
#<INPUT TYPE="HIDDEN" NAME="action" VALUE="1">
#<INPUT TYPE="FILE" NAME="file1">
#<INPUT TYPE="SUBMIT" VALUE="Host It"> <br>
#<INPUT TYPE="text" NAME="img_resize" SIZE="4" MAXLENGTH="4">
#</FORM>
# get contents of the gif
set fd [open ./logo125.gif]
fconfigure $fd -translation binary
set image [read $fd]
close $fd
# set up other fields
array set fields {
MAX_FILE_SIZE " "
action 1
img_resize "100%"
}
# format the image and form
puts [form-data::format file1 "logo125.gif" $image image/gif {*}[array get fields]]
}BR 2005-06-02 - This proc works around the "header-problem" and it uses a temporary file for the body data to connect to the http package. package require http
proc form-data::post {url field type file {params {}} {headers {}}} {
# get contents of the file
set fd [open $file r]
fconfigure $fd -translation binary
set content [read $fd]
close $fd
# format the file and form
set message [eval [list form-data::format \
$field [file tail $file] $content $type] \
$params]
# parse the headers out of the message body
set message [split [string map {"\r\n\r\n" "\1"} $message] "\1"]
set headers_raw [lindex $message 0]
set body [join [lrange $message 1 end] "\r\n\r\n"]
set headers_raw [string map {"\r\n " " " "\r\n" "\n"} $headers_raw]
regsub { +} $headers_raw " " headers_raw
#set headers {} -- initial value comes from parameter
foreach line [split $headers_raw "\n"] {
regexp {^([^:]+): (.*)$} $line all label value
lappend headers $label $value
}
# get the content-type
array set ha $headers
set content_type $ha(Content-Type)
unset ha(Content-Type)
set headers [array get ha]
# create a temporary file for the body data (getting the temp directory
# is more involved if you want to support Windows right)
set datafile "/tmp/post[pid]"
set data [open $datafile w+]
fconfigure $data -translation binary
puts -nonewline $data $body
seek $data 0
# POST it
set token [http::geturl $url -type $content_type -binary true \
-headers $headers -querychannel $data]
http::wait $token
# cleanup the temporary
close $data
file delete $datafile
return $token
}[Erl] 2005-08-09 (August 9)I have submitted a patch (#1254934 in SourceForge) to mime.tcl to fix the extra line feed added to attachments. Just line feeds removed in two places. I created a SourceForge tcllib bug #1254937 for it as well.Further, the form-data-post function above has a problem with binary files, because it replaces all 0x01 bytes with a \r\n sequence. Here is a modified version, which does not require an external file either. proc form-data::post {url field type file {params {}} {headers {}}} {
# get contents of the file
set fd [open $file r]
fconfigure $fd -translation binary -encoding binary
set content [read $fd]
close $fd
# format the file and form
set message [eval [list form-data::format \
$field [file tail $file] $content $type] \
$params]
# parse the headers out of the message body because http get url wants
# them as a separate parameter
set headerEnd [string first "\r\n\r\n" $message]
incr headerEnd 1
set bodystart [expr $headerEnd + 3]
set headers_raw [string range $message 0 $headerEnd]
set body [string range $message $bodystart end]
set headers_raw [string map {"\r\n " " " "\r\n" "\n"} $headers_raw]
regsub { +} $headers_raw " " headers_raw
foreach line [split $headers_raw "\n"] {
regexp {^([^:]+): (.*)$} $line all label value
lappend headers $label $value
}
# get the content-type
array set ha $headers
set content_type $ha(Content-Type)
unset ha(Content-Type)
set headers [array get ha]
# POST it
set token [http::geturl $url -type $content_type -binary true \
-headers $headers -query $body]
http::wait $token
return $token
}[vinniyo] - 2013-10-26 02:27:13Has anyone seen any method of upload large files as a xml part(500MB to 2GB)? I have been unsucessful. ---Correction. The Header-Problem from BR 2005-06-02 has a temp file that it writes to and uses "seek $data 0" for posting. Great code. Here is my youtube Data API video uploader derived from everyone elses code:
package require mime
package require xmlgen
namespace import ::xmlgen::*
proc format_upload {file_location title description category keywords} {
set authx [get_refresh]
set del_key <>
declaretag entry
declaretag media:group
declaretag media:title
declaretag media:description
declaretag media:category
declaretag media:keywords
xmlgen::buffer xml_meta {entry xmlns=http://www.w3.org/2005/Atom xmlns:media=http://search.yahoo.com/mrss/ xmlns:yt=http://gdata.youtube.com/schemas/2007 ! {
media:group ! {
media:title type=plain - $title
media:description type=plain - $description
media:category scheme=http://gdata.youtube.com/schemas/2007/categories.cat - $category
media:keywords - $keywords
}
}}
set parts {}
lappend parts [mime::initialize -canonical {application/atom+xml} -string $xml_meta -encoding binary]
lappend parts [mime::initialize -canonical {video/avi} -string "video_file" -encoding binary]
set mime [mime::initialize -canonical {multipart/related} -parts $parts]
set packaged [mime::buildmessage $mime]
::mime::finalize [lindex $parts 0]
::mime::finalize [lindex $parts 1]
::mime::finalize $mime
puts "getting header"
update
set headerEnd [string first "\r\n\r\n" $packaged]
incr headerEnd 1
set bodystart [expr $headerEnd + 3]
set headers_raw [string range $packaged 0 $headerEnd]
set bodyend [string first "video_file" $packaged]
set body [string range $packaged $bodystart $bodyend-1]
set ender [string range $packaged $bodyend+10 end]
set headers_raw [string map {"\r\n " " " "\r\n" "\n"} $headers_raw]
regsub { +} $headers_raw " " headers_raw
foreach line [split $headers_raw "\n"] {
regexp {^([^:]+): (.*)$} $line all label value
lappend headers $label $value
}
array set ha $headers
set content_type $ha(Content-Type)
set datafile [file join tmp post[pid]]
set data [open $datafile w+]
fconfigure $data -translation binary
puts -nonewline $data $body
set input [open $file_location r]
fconfigure $input -translation binary
while {[gets $input line] != -1} {puts $data $line}
close $input
puts -nonewline $data $ender
seek $data 0
puts "uploading now"
update
if {[catch {set token [http::geturl "http://uploads.gdata.youtube.com/feeds/api/users/default/uploads" -binary true -type $content_type -headers "Slug afv.mp4 Connection close GData-Version 2 X-GData-Key key=$del_key Authorization {Bearer $authx}" -querychannel $data]} error]} {puts "error from geturl in upload: $error"; return 0}
http::wait $token
puts "done with upload"
update
set post_return [http::data $token]
http::cleanup $token
close $data
file delete -- $datafile
return $post_return
}
proc get_refresh {} {
set refresh_token <>
set client_secret <>
set client_id <>
set token [http::geturl https://accounts.google.com/o/oauth2/token -headers "Content-Type application/x-www-form-urlencoded" -query client_id=$client_id&client_secret=$client_secret&refresh_token=$refresh_token&grant_type=refresh_token]
set info [http::data $token]
http::cleanup $token
if {[regexp {\"access_token\"...\"([^\"]*)\"} $info dump access]} {
return $access
} else {
puts "refreshing didnt work :( Im poor $info"
return 0
}
}
proc upload {t3_id title username} {
regsub -all {[\"\;\'\-\]\[$^?+*()|\\%&#]} $title "" title
set desc $title
set desc_db $title
append desc \n\n[annotate]
if {[string length $title] > 60} {set title "[string range $title 0 56]..."}
set keywords [get_longest $desc_db 2]
set keywords [string map {" " ", "} $keywords]
set data [format_upload [file join O: AFV $t3_id.f4v] $title $desc Comedy $keywords]
switch -regexp -- $data {
<yt:videoid> {regexp {<yt:videoid>([^<]*)</yt:videoid>} $data dump videoID; write_db $username $t3_id $videoID $desc_db; wait 2}
too_many_recent_calls {puts "waiting 1 min Too many calls"; wait 60}
Forbidden {puts "Forbidden..Stopping"; vwait forever}
default {puts "UPLOAD ERROR IS: $data"; return 0}
}
}
