Forum OpenACS Development: File Upload Fails on Windows OACS Installaion

We created a test Windows 11 based OACS server (OACS 5.10.0 NS 5.0.0) via posted installation link [1]. When we upload a file via file-storage instance we get "Invalid input".

Our config file has a tmpdir entry:

ns_param   tmpdir             c:/naviserver/servers/${server}/tmp

Log shows it is using a different directory. How can we change it?:

[29/Oct/2025:14:10:08][16004.5230][-conn:openacs:default:1:18-] Debug(sql): pool pool1 duration 0.000000 secs: '
:                select acs_permission.permission_p('964', '729', 'read') from dual
:            '
[29/Oct/2025:14:10:08][16004.5230][-conn:openacs:default:1:18-] Warning: They tried to sneak in invalid tmpfile 'C:/WINDOWS/TEMP/nsd-XXXXXX20228.TMP'
:        called from ad_page_contract {} 1 {} 0 __unknown__ {\n    page to add a new file to the syste...} {\n    file_id:naturalnum,optional,notnull...} -properties {\n    folder_id:onevalue\n    context:one...} -validate \\n\ \ \ \ file_id_or_folder_id\ \{\\n\ \ \ \ \ \ \ \ if\ ...
:        called from template::adp_parse c:/naviserver/servers/openacs//packages/file-storage/www/file-add {}
:        called from adp_parse_ad_conn_file
:        called from rp_serve_concrete_file c:/naviserver/servers/openacs/packages/file-storage/www/file-add.adp
:        called from rp_serve_abstract_file 0 0 .* c:/naviserver/servers/openacs/packages/file-storage/www/file-add
:        called from rp_handle_request
:        called from rp_handler
:            POST http://localhost:8000/file-storage/file-add? referred by 'http://localhost:8000/file-storage/file-add?folder_id=979'; peer ::1 user_id 729

[1] https://openacs.org/xowiki/openacs-system-install-windows-server

Collapse
Posted by Gustaf Neumann on
Most likely, the function "security::safe_tmpfile_p&source_p" fails under windows.

https://openacs.org/api-doc/proc-view?proc=security::safe_tmpfile_p&source_p=1

The definition looks sane to me, it is not obvious, why this should not work under windows. I have no Windows machine around. Raul, can you check, which clause fails in your installation (e.g. adding log statements).

all the best
-g

Collapse
Posted by Raul Rodriguez on
Hi,

We added logging to:

ad_proc security::safe_tmpfile_p {
    -must_exist:boolean
    tmpfile
} {

    Checks that a file is a safe tmpfile, that is, it belongs to the
    configured tmpdir.

    When the file exists, we also enforce additional criteria:
    - file must belong to the current system user
    - file must be readable and writable by the current system user

    @param tmpfile absolute path to a possibly existing tmpfile
    @param must_exist make sure the file exists

    @return boolean
} {
    #
    # Ensure no ".." in the path
    #
                ns_log Notice ">>> STARTING TMPFILE tmpfile: $tmpfile"
    set tmpfile [ns_normalizepath $tmpfile]
    set tmpdir [string trimright [ns_config ns/parameters tmpdir] /]
                ns_log Notice ">>> UPLOADCHECK tmpfile = $tmpfile"
                ns_log Notice ">>> UPLOADCHECK tmpdir = $tmpdir"

Got:

[31/Oct/2025:09:34:58][22512.56d4][-conn:openacs:default:4:2-] Notice: >>> STARTING TMPFILE tmpfile:
C:/WINDOWS/TEMP/nsd-XXXXXX29771.TMP
[31/Oct/2025:09:34:58][22512.56d4][-conn:openacs:default:4:2-] Notice: >>> UPLOADCHECK tmpfile = c:/WINDOWS/TEMP/nsd-XXXXXX29771.TMP
[31/Oct/2025:09:34:58][22512.56d4][-conn:openacs:default:4:2-] Notice: >>> UPLOADCHECK tmpdir = c:/naviserver/servers/openacs/tmp
[31/Oct/2025:09:34:58][22512.56d4][-conn:openacs:default:4:2-] Notice: >>> UPLOADCHECK direct child False
[31/Oct/2025:09:34:58][22512.56d4][-conn:openacs:default:4:2-] Debug(sql): pool pool1 duration 0.000000 secs: '
:                    select apm_parameter_values.attr_value
:                    from   apm_parameters, apm_parameter_values
:                    where  apm_parameter_values.package_id = '177'
:                    and    apm_parameter_values.parameter_id = apm_parameters.parameter_id
:                    and    apm_parameters.parameter_name = 'SuppressHttpPort'
:                '
[31/Oct/2025:09:34:58][22512.56d4][-conn:openacs:default:4:2-] Warning: They tried to sneak in invalid tmpfile 'C:/WINDOWS/TEMP/nsd-XXXXXX29771.TMP'
:        called from ad_page_contract {} 1 {} 0 __unknown__ {\n    page to add a new file to the syste...} {\n    file_id:naturalnum,optional,notnull...} -properties {\n    folder_id:onevalue\n    context:one...} -validate \\n\ \ \ \ file_id_or_folder_id\ \{\\n\ \ \ \ \ \ \ \ if\ ...
:        called from template::adp_parse c:/naviserver/servers/openacs//packages/file-storage/www/file-add {}
:        called from adp_parse_ad_conn_file
:        called from rp_serve_concrete_file c:/naviserver/servers/openacs/packages/file-storage/www/file-add.adp
:        called from rp_serve_abstract_file 0 0 .* c:/naviserver/servers/openacs/packages/file-storage/www/file-add
:        called from rp_handle_request
:        called from rp_handler
:            POST http://localhost:8000/file-storage/file-add? referred by 'http://localhost:8000/file-storage/file-add?folder_id=979'; peer ::1 user_id 729

We also ran automated testing on acs-templating validate_file. Got the following Errors:

                FAILED Is Afile C:/WINDOWS/TEMP/oacs53344.TMP text/plain a file?: false
                FAILED Is A/file C:/WINDOWS/TEMP/oacs53344.TMP a a file?: false

Thank you for your help

Collapse
Posted by Gustaf Neumann on

What i can see from the output is that it looks like the Tcl file tempfile subcommand looks broken. The command below

::file tempfile tmpFileName $template

seems to be ignoring the privided template mostly. From the commands below,

set template "c:/naviserver/servers/openacs/tmp/nsd-XXXXXX"
set file [::file tempfile tmpFileName $template]

the expected tmpFileName is wrong

expected: c:/naviserver/servers/openacs/tmp/nsd-29771
you got:  c:/WINDOWS/TEMP/nsd-XXXXXX29771.TMP

Please double-check this. Of course, the numeric part will differ. If my hypothesis is correct, then please replace the function ns_opentmpfile in ns/tcl/form.tcl with the following

proc ns_opentmpfile {varFilename {template ""}} {
  upvar $varFilename tmpFileName
  if {$template eq ""} {
     set template [ns_config ns/parameters tmpdir]/nsd-XXXXXX
  }
  if {$::tcl_platform(platform) eq "windows"} {
      set tmpFileName [ns_mktemp -nocomplain $template]
      return [open $tmpFileName {RDWR CREAT EXCL}]
  } else {
     return [::file tempfile tmpFileName {*}$template]
  }
}

This is not the perfect solution and violates security considerations (creating the time file should be an atomic operation, we do it here like in old Tcl implementations), but it is as good as it gets with reasonable effort.

If my suspicion is true, I would regard this as a bug in Tcl, although the documentation in Tcl is very vague on this point. It says:

though some platforms may ignore some or all of these parts and use a built-in default instead.”

What version of Tcl are you using?

All the best
-g

Collapse
Posted by Raul Rodriguez on
Hi,
We applied the change to
C:\naviserver\tcl\form.tcl

and got this:

wrong # args: should be "ns_mktemp ?template?"
    while executing
"ns_mktemp -nocomplain $template"
    (procedure "ns_opentmpfile" line 7)
    invoked from within
"ns_opentmpfile tmpfile"
    (procedure "ns_getform" line 53)
    invoked from within
"ns_getform"
    (procedure "::nsf::procs::ad_page_contract" line 315)
    invoked from within
"ad_page_contract {
    page to add a new file to the system

    @author Kevin Scaldeferri (mailto:kevin@arsdigita.com)
    @creation-date 6 Nov 2000
    @cv..."
    ("uplevel" body line 2)
    invoked from within
"uplevel {
    ad_page_contract {
    page to add a new file to the system

We removed:

-nocomplain

The updated procedure is:

proc ns_opentmpfile {varFilename {template ""}} {
  upvar $varFilename tmpFileName
  if {$template eq ""} {
     set template [ns_config ns/parameters tmpdir]/nsd-XXXXXX
  }
  if {$::tcl_platform(platform) eq "windows"} {
      set tmpFileName [ns_mktemp $template]
      return [open $tmpFileName {RDWR CREAT EXCL}]
  } else {
     return [::file tempfile tmpFileName {*}$template]
  }
}

We uploaded a file and got this:

[04/Nov/2025:09:15:25][3300.4b04][-conn:openacs:default:0:16-] Notice: >>> STARTING TMPFILE tmpfile: c:/naviserver/servers/openacs/tmp/nsd-a19204
[04/Nov/2025:09:15:25][3300.4b04][-conn:openacs:default:0:16-] Notice: >>> UPLOADCHECK tmpfile = c:/naviserver/servers/openacs/tmp/nsd-a19204
[04/Nov/2025:09:15:25][3300.4b04][-conn:openacs:default:0:16-] Notice: >>> UPLOADCHECK tmpdir = c:/naviserver/servers/openacs/tmp
[04/Nov/2025:09:15:25][3300.4b04][-conn:openacs:default:0:16-] Notice: >>> UPLOADCHECK file owned False
[04/Nov/2025:09:15:25][3300.4b04][-conn:openacs:default:0:16-] Warning: They tried to sneak in invalid tmpfile 'c:/naviserver/servers/openacs/tmp/nsd-a19204'
:        called from ad_page_contract {} 1 {} 0 __unknown__ {\n    page to add a new file to the syste...} {\n    file_id:naturalnum,optional,notnull...} -properties {\n    folder_id:onevalue\n    context:one...} -validate \\n\ \ \ \ file_id_or_folder_id\ \{\\n\ \ \ \ \ \ \ \ if\ ...
:        called from template::adp_parse c:/naviserver/servers/openacs//packages/file-storage/www/file-add {}
:        called from adp_parse_ad_conn_file
:        called from rp_serve_concrete_file c:/naviserver/servers/openacs/packages/file-storage/www/file-add.adp
:        called from rp_serve_abstract_file 0 0 .* c:/naviserver/servers/openacs/packages/file-storage/www/file-add
:        called from rp_handle_request
:        called from rp_handler
:            POST http://localhost:8000/file-storage/file-add? referred by 'http://localhost:8000/file-storage/file-add?folder_id=979'; peer ::1 user_id 729
Collapse
Posted by Raul Rodriguez on
We reverted this logging logic after our test:
  ns_log Notice ">>> STARTING TMPFILE tmpfile: $tmpfile"
    set tmpfile [ns_normalizepath $tmpfile]
    set tmpdir [string trimright [ns_config ns/parameters tmpdir] /]
  ns_log Notice ">>> UPLOADCHECK tmpfile = $tmpfile"
  ns_log Notice ">>> UPLOADCHECK tmpdir = $tmpdir"

    if {[ad_file dirname $tmpfile] ne $tmpdir} {
        #
        # File is not a direct child of the tmpfolder: not safe
        #
    ns_log Notice ">>> UPLOADCHECK direct child False"
        #return false
    }

    if {![ad_file exists $tmpfile]} {
        #
        # File does not exist yet: safe, unless we demand for the file
        # to exist.
        #
    ns_log Notice ">>> UPLOADCHECK file not exists"
        #return [expr {!$must_exist_p}]
    }

    if {![ad_file owned $tmpfile]} {
        #
        # File does not belong to us: not safe
        #
    ns_log Notice ">>> UPLOADCHECK file owned False"
        #return false
    }

    if {![ad_file readable $tmpfile]} {
        #
        # We cannot read the file: not safe
        #
    ns_log Notice ">>> UPLOADCHECK file can't read"
        #return false
    }

    if {![ad_file writable $tmpfile]} {
        #
        # We cannot write the file: not safe
        #
    ns_log Notice ">>> UPLOADCHECK file can't write"
        #return false
    }

    #
    # The file is safe
    #
    return true

Collapse
Posted by Gustaf Neumann on

wrong # args: should be "ns_mktemp ?template?"

This indicates, that you are not using NaviServer 5. What version of NaviServer and Tcl are you using?

With NaviServer 5, we see:

%      set template [ns_config ns/parameters tmpdir]/nsd-XXXXXX 
/var/folders/w3/w_b0s6rj11n5q1fnrpxwl07m0000gn/T/nsd-XXXXXX
%       set tmpFileName [ns_mktemp $template]
[05/Nov/2025:17:16:15][26758.16f81b000][-command-] Deprecated: 'ns_mktemp /var/folders/w3/w_b0s6rj11n5q1fnrpxwl07m0000gn/T/nsd-XXXXXX' is deprecated since it poses a potential race condition and security risk; consider using 'ns_uuid' or 'file tempfile' instead
/var/folders/w3/w_b0s6rj11n5q1fnrpxwl07m0000gn/T/nsd-mcxy0v
% 
% 
% set tmpFileName [ns_mktemp -nocomplain $template]
/var/folders/w3/w_b0s6rj11n5q1fnrpxwl07m0000gn/T/nsd-WzF5Xh
...

From your post, it is not clear to me, if your setup is now working, or if you need more help.

I am currently on a boat on the Mekong in Cambodia, i might not be able to respond for a couple of days.

Collapse
Posted by Raul Rodriguez on
Hi,
Per our windows installation:
https://openacs.org/xowiki/openacs-system-install-windows-server
 • This is Version 5.16.0 (4th of September 2023) of Windows-OpenACS port and it consists of:
naviserver-5.0.0 – including
   • nsdbpg 2.8
   • nsoracle 2.9
   • nsssl 2.3
 • tcl-8.6.13
 • tk-8.6.13
 • tDOM-0.9.1
 • xotcl-2.3.0 (nsf 2.3.0)
 • libthread-2.8.8
 • tcllib-1.20
 • zlib-1.3
 • postgresql-16.4
 • openssl-3.1.3
 • OpenACS 5.10.1
 • External binaries:
a. Strawberry Perl 5.32.1.1 including MinGW 64 binaries (http://strawberryperl.com/)
b. Curl 7.47.0 (http://curl.haxx.se/)
c. brotli v1.0.7 (https://github.com/google/brotli)

Our version of Naviserver is 5.0.0 and Tcl version: 8.6.13.

Our ad_file fails on:

    if {![ad_file owned $tmpfile]} {
        #
        # File does not belong to us: not safe
        #
    ns_log Notice ">>> UPLOADCHECK file owned False"
       # return false
    }

When we ignore this check the file does upload. Since, this looks like another issue specific to TCL with a windows installation, we are working on creating another test server with linux.

Thank you

Collapse
Posted by Gustaf Neumann on
Can it be that you have installed multiple versions of NaviServer on your Windows machine and that you have changed the code in an 4.99 instance? The reason, i am writing this is, because of the error you got with "-nocomplain" (in posting #5). You are saying that you use NaviServer 5.0.0, but this version has the flag (see: [1]).

Anyhow, i have now committed the change bypassing the Tcl-bug under windows into the "main" and "release/5.0" branches of NaviServer. Additionally, I have committed a change to the HEAD and the oacs-5-10 branches to bypass the "ownership" test under windows. Windows seems to have a different understanding of ownership, maybe this interacts with the creation flags.

-g

[1] https://github.com/naviserver-project/naviserver/blob/naviserver-5.0.0/nsd/tclfile.c#L235