Forum OpenACS Development: Saving base64 url to a image file

Collapse
Posted by Iuri Sampaio on

Hi there, I'm trying to save the following base64 URL https://iurix.com/img.base64 to an image file, however, the final PNG image displays an empty black square.

Downloading the PNG file and trying to open in an image viewer tells that the file has been corrupted.

I've tried to convert it with and without the first part "data:image/png;base64".

Plus, I've used both methods nsbase64urldecode and nsbase64decode

None of them worked successfully.

How must I convert from base64 to PNG ?

Here it is the chunk of code to convert from base64url to binary PNG

        set image_base64 [split $arr(image) ","]
        set mime_type [lindex $image_base64 0]
        switch $mime_type {
            case "data:image/png;base64" {
                set mime_type "image/png"
            }
            case "data:image/jpg;base64" "data:image/jpeg;base64" {
                set mime_type "image/jpg"
            }
            case "data:image/gif;base64" {
                set mime_type "image/gif"
            }
            default {
                set mime_type ""
            }
        }
        set tmp_file [ns_mktemp]
        set fp [open $tmp_file w]
        puts $fp [ns_base64urldecode [lindex $image_base64 1]]
        close $fp
Collapse
Posted by Iuri Sampaio on
By the way, the original base64url opens just fine in the web browser.
To confirm, you can copy the follwing content https://iurix.com/img.base64 in the browser.
Collapse
Posted by Steffen Tiedemann Christensen on

Hi Iuru,

There are a few quirks in your code sample (ns_mktemp takes a template argument; the switch statement shouldn't include the "case" part) -- but leaving that aside I think you need to switch your file socket to be binary using fconfigure.

This is probably a good starting point to solve your problem (with $raw being the data from the url):

 lassign [split $raw ","] header data
 
 # NOTE: Much better data validation is required here

 switch -exact $header {
   "data:image/png;base64" {
      set mime_type "image/png"
   }
   default {
     error "unsupported type"
   } 
 }

 set filename [ns_mktemp /tmp/base64-image-XXXXXX]

 set fd [open $filename w]
 fconfigure $fd -translation binary 
 package require base64
 puts $fd [base64::decode $data]
 close $fd

 ns_returnfile 200 $mime_type $filename
Collapse
Posted by Iuri Sampaio on
Thanks Steffen,
Yep, "fconfigure $fd -translation binary" was key in this process.
and not only file socket, but also switching ns_* procs to TCL libraries directly.

Indeed, your writing is much cleaner. And it works beautifuly! Thanks again!

Best wishes,
I

Collapse
Posted by Gustaf Neumann on

One can also use the built-in bas64decode, which is much faster:

  lappend _ [time {base64::decode $data} 1000]
  lappend _ [time {ns_base64decode -binary $data} 1000]

returns

{1757.215183 microseconds per iteration} {343.221319 microseconds per iteration}

In case, the content has just to be displayed, the file handing can be omitted.

 set r [ns_http run https://iurix.com/img.base64]
 lassign [split [dict get $r body] ,] header data

 switch -exact $header {
     "data:image/png;base64" { set mime_type "image/png" }
     default { error "unsupported type"} 
 }

 ns_return -binary 200 $mime_type [ns_base64decode -binary $data]
Collapse
Posted by Iuri Sampaio on
Hi Gustaf,
That was my very first attempt. The problem was exactly in the proc ns_base64decode.
...
[ns_base64decode -binary $data]
...

It returns an error regarding "-binary" switch (see logs bellow). Then I started to research for variations of that approach.

All them were frustrated because "-binary" switch is essential in the conversion process, whether at the creation of file descriptor
...
fconfigure $fd -translation binary
...

or at the data conversion itself

[ns_base64decode -binary $data]

NS realease is: NaviServer/4.99.18 (tar-4.99.18) running

Best wishes,
I

[02/Sep/2020:12:53:35][28801.7efbf3d70700][-conn:qonteo:0:742-] Error: wrong # args: should be "ns_base64decode string"
while executing
"ns_base64decode -binary $data"
("uplevel" body line 49)
invoked from within
"uplevel {

ns_log Notice "Running REST upload-image"

# Validate and Authenticate JWT
qt::rest::jwt::validation_p

if {[ns_conn method] eq "POST"..."
(procedure "code::tcl::/var/www/qonteo/oacs//packages/qt-rest/www/upload..." line 2)
invoked from within
"code::tcl::$__adp_stub"
("uplevel" body line 12)
invoked from within
"uplevel {

if { [file exists $__adp_stub.tcl] } {

# ensure that data source preparation procedure exists and is up-to-date
..."
(procedure "adp_prepare" line 2)
invoked from within
"adp_prepare"
invoked from within
"template::adp_parse $themed_template {}"
(procedure "adp_parse_ad_conn_file" line 14)
invoked from within
"$handler"
("uplevel" body line 2)
invoked from within
"uplevel $code"
invoked from within
"ad_try {
$handler
} ad_script_abort val {
# do nothing
}"
invoked from within
"rp_serve_concrete_file [ad_conn file]"
(procedure "::nsf::procs::rp_serve_abstract_file" line 60)
invoked from within

Collapse
Posted by Gustaf Neumann on
Use a more recent version: the "-binary" flag for ns_base64decode is supported since the release of 4.99.19 (Jan this year) [1].

-gn

[1] https://bitbucket.org/naviserver/naviserver/src/081829e43606b0e50baf3f258649ce4311358e10/NEWS#lines-220

Collapse
Posted by Iuri Sampaio on
Nice!
NS has been upgraded, and "-binary" works like a charm!
Best wishes,
I