1 # ===================================================================== 2 # putAttachment: W-TW file attachment upload/update processor. 3 # 4 # Copyright (c) 2007,2012,2022 Carlo Strozzi 5 # 6 # This program is free software; you can redistribute it and/or modify 7 # it under the terms of the GNU General Public License as published by 8 # the Free Software Foundation; version 2 dated June, 1991. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this program; if not, write to the Free Software 17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 # 19 # ===================================================================== 20 21 # ===================================================================== 22 # Local variables and functions 23 # ===================================================================== 24 25 cgi.group = () 26 cgi.group.literal = () 27 cgi.page = () 28 cgi.page.literal = () 29 cgi.descr = () 30 cgi.author = () 31 cgi.attname = () # the (sanitized) file name supplied by the client. 32 cgi.fileext = bin 33 cgi.attachment = /dev/null # the tmp file created by CSA 34 cgi.where = web # default location. 35 cgi.allow = () 36 37 # ===================================================================== 38 # Main program 39 # ===================================================================== 40 41 csaGetArgs POST 42 43 . $CSA_ROOT/lib/group-stuff.rc 44 45 ~ $'cgi.attachment' /dev/null && csaExit.fault 0038 46 47 test -s $'cgi.attachment' || csaExit.fault 1051 48 49 . $CSA_ROOT/lib/group-editor.rc 50 51 ~ ,$TNS_AUTH_GRP, *,editor,* || csaExit.needauth 52 53 csaTrue $TNS_ATTACH_DISABLE && csaExit.fault 403 0022 54 55 # Page name mandatory if private attachment. 56 57 if (~ $'cgi.where' wiki) { 58 59 ~ $'cgi.page' () && csaExit.fault 1001 60 61 tw_stem = $tw_gstem 62 63 # Check for page existence. 64 65 tw_pstem = $tw_gstem/$'cgi.page' 66 tpl.var.tw.page.unx = $'cgi.page' 67 68 # Load page meta-data. 69 keysearch $'cgi.page' $tw_gstem/page+dat | 70 csa-tbl2rc --prefix tbl_page. > $tmp1 71 . $tmp1 72 73 # The specified page MUST exist or it may not have attachments. 74 ~ $'tbl_page.k_page' () && csaExit.fault 1011 $'cgi.page' 75 76 # Set template vars to their final values. 77 tpl.var.tw.page = $'tbl_page.p_name' 78 tpl.var.tw.page.object = $'tpl.var.tw.page' 79 tpl.var.html.title = $'tpl.var.tw.group'/$'tpl.var.tw.page' 80 81 } else { 82 83 cgi.page = () # mandatory if public. 84 85 # If we are being requested to upload a public object the target 86 # public directory must already exist. Let's be very careful on 87 # that directory, as it may not be owned by us. 88 89 tw_stem = $CSA_DOCROOT/$CSA_LANG/$TNS_ATTACH_PUBDIR/$'cgi.group' 90 test -d $tw_stem || csaExit.fault 0037 1 $CSA_PGM($#CSA_PGM) 91 touch $tw_stem/.touch || csaExit.fault 0003 touch 92 } 93 94 # Add more forbidden file extensions to group+cf as needed. 95 ~ $'cgi.fileext' exe tmp $TNS_FORBIDDEN_EXT && csaExit.fault --back 1007 96 97 # File names beginning with tw-* are reserved and must be left alone. 98 # This of great importance especially in case a tb.html is uploaded. 99 # The associated .tw.html will contain inclusion directives and will be 100 # parsed with the "--file-root" option set, so we must make sure that 101 # no tw-files can be included from the public upload area, otherwise we 102 # would effectively be granting webmaster privileges to group editors. 103 # There's no need to prevent the upload of additional reserved templates, 104 # such as {tw,tp,te}.html, because they will never be sought for in the 105 # group upload directory anyway. See "lib/tpl-stuff.rc" for more info. 106 107 ~ $'cgi.attname' tw-* && csaExit.fault --back 1007 108 109 # Prevent uploading of TW database files and RCS stuff, just in case. 110 # This is sensible checking, since when I allowed "+" in uploaded file 111 # names, see lib/rpcio/putAttachment.awk . 112 ~ $'cgi.attname' *+[a-z][a-z] *+[a-z][a-z][a-z] *,v && 113 csaExit.fault --back 1007 114 115 # This should not occur, but if it did occur it would be a real 116 # pain, especially with page-level attachments because they get 117 # also mapped in the database, so better to be safe than sorry. 118 119 ~ $'cgi.attname' () && csaExit.fault 0066 120 121 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 122 123 if (!~ $'cgi.page' ()) { 124 125 # If no access restrictions were specified for a private attachment, 126 # the latter will inherit those of the page it belongs to. This means 127 # that while the page restrictions and the attachment restrictions 128 # may differ, it is not possibile to have a completely unrestricted 129 # private attachment if the associated page is somehow restricted. 130 131 ~ $'cgi.allow' () && cgi.allow = $'tbl_page.p_allow' 132 133 # Prepare updated values for the page table. 134 tbl_page.k_page = $'cgi.page' 135 tbl_attach.k_attach = wiki/$'cgi.attname' 136 tbl_attach.a_descr = $'cgi.descr' 137 tbl_attach.a_author = $'cgi.author' 138 tbl_attach.a_mtime = $CSA_TIME_ISO8601 139 tbl_attach.a_modip = $REMOTE_ADDR 140 tbl_attach.a_allow = $'cgi.allow' 141 142 # Set Principal Lock Semaphore(s) (PLS). 143 csaLock $tw_gstem/page+dat || csaExit.fault 144 145 # Create target table if it does not yet exist. 146 if (!csaIsFullPath --exists --quiet $tw_pstem+att) { 147 maketable --input \ 148 $CSA_ROOT/lib/attachment.xrf > $tw_pstem+att || 149 csaExit.fault 0003 maketable 150 } 151 152 # Compute unique node ID if new page attachment. Note that no nodes 153 # are created for group-level public attachments, i.e. pageless API 154 # objects (see further down) since, beside being completely useless, 155 # it would also be impossible to accomplish. 156 157 if (!keysearch --test $'tbl_attach.k_attach' $tw_pstem+att) { 158 159 makeNode $CSA_LANG/$'cgi.group'/$'cgi.page' $'tbl_attach.k_attach' 160 161 tbl_attach.k_node = $node_num 162 163 } else { 164 csaIsConfirmed || csaExit.fault --back 1050 165 } 166 167 # Update the page attachment meta-data table. 168 csaOpen --fast --relaxed $tw_pstem+att || csaExit.fault 169 tmp_attach = $CSA_RESULT 170 171 envtotable --match '^tbl_attach__2e[a-z]' --strip-names '^tbl_attach__2e' | 172 updtable --stdin --key-columns k_attach $tw_pstem+att | 173 sorttable > $tmp_attach 174 175 csaStatus || csaExit.fault 0003 envtotable/updtable/sorttable 176 177 # Update attachment counters. 178 att_cnt = `{awktable -i $tmp_attach \ 179 '-vre_=^wiki/' -- '$k_attach ~ re_ {i_++}END{print i_/1}'} 180 181 tbl_page.p_npriv = $att_cnt 182 183 # Update the page meta-data table. 184 csaOpen --fast $tw_gstem/page+dat || csaExit.fault 185 tmp_pages = $CSA_RESULT 186 187 envtotable --match '^tbl_page__2e[a-z]' --strip-names '^tbl_page__2e' | 188 updtable --stdin --key-columns k_page $tw_gstem/page+dat | 189 sorttable > $tmp_pages 190 191 csaStatus || csaExit.fault 0003 envtotable/updtable/sorttable 192 } 193 194 # Make sure the public attachment directory cannot be browsed outside 195 # the relevant CSA dialogs. Of course the user may want to upload a 196 # customized index.html here. 197 198 csaIsFullPath --exists --quiet $tw_stem/index.html || 199 cp $tw_dstem/tw-index-no-browse.html $tw_stem/index.html 200 201 # Schedule the actual attachment file creation/update. Make sure 202 # permissions are restored, in case they were crippled by 203 # 'deleteAttachment' (see the latter for more info). No TPC 204 # is really needed here but I need to apply locking if pageless 205 # request, as no PLS is in effect in that case and two clients 206 # may be uploading the same file at the same time. 207 208 if (~ $'cgi.page' ()) { 209 210 csaLock $tw_stem/$'cgi.attname' || csaExit.fault 211 212 csaIsFullPath --quiet --exists $tw_stem/$'cgi.attname' && 213 !csaIsConfirmed && csaExit.fault 1050 214 215 # I cannot rely on csaOnCommit if pageless requests, because 216 # there's nothing to commit so the function would be a noop. 217 # OTOH, no database tables are involved here, to there's 218 # really no big risk of inconsistency. 219 220 cp $'cgi.attachment' $tw_stem/$'cgi.attname' || csaExit.fault 0003 cp 221 chmod go+r $tw_stem/$'cgi.attname' || csaExit.fault 0003 chmod 222 223 tpl.var.tw.url = \ 224 $CSA_URL/$CSA_LANG/$TNS_ATTACH_PUBDIR/$'cgi.group'/$'cgi.attname' 225 226 # Local attachment postprocessor currently only supported for public 227 # attachments, as private ones require tweaking with the database. 228 229 ~ $TNS_ATTACH_PROC () || { 230 csaPrintMsg 0060 $TNS_ATTACH_PROC 231 . $CSA_ROOT/local/$TNS_ATTACH_PROC 232 } 233 234 } else { 235 236 # Update the page-attachments static view. Note that this view may 237 # list attachments which content is not accessible to the viewing 238 # client (based on "attachment:a_allow"), so at least the attachment 239 # URLs and descriptions will be publicly visible if the editor decides 240 # to include this view in any templates. This is in line with the 241 # current TW specs. 242 243 csaOpen --fast --relaxed $tw_pstem-att+xml || csaExit.fault 244 tmp1 = $CSA_RESULT 245 csaAwkCmd pageAttachments.awk 246 getcolumn -i $tmp_attach k_attach a_mtime a_descr | 247 $CSA_RESULT > $tmp1 248 249 csaStatus || csaExit.fault 0003 getcolumn/AWK 250 251 # Hidden pages will have this static view void. 252 ~ $'tbl_page.p_descr' -* || tpl.include.tw.page.att = $tw_pstem-att+xml 253 254 # (re-)build the local DB if this is the relevant field-schema file. 255 !~ $TNS_DB_SCHEMA () && ~ $'cgi.attname' $TNS_DB_SCHEMA.csv && 256 . $CSA_ROOT/lib/makedb.rc 257 258 csaOnCommit 'cp '$'cgi.attachment'^' '$tw_pstem+$'cgi.attname' 259 csaOnCommit 'chmod go+r '$tw_pstem+$'cgi.attname' 260 } 261 262 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 263 264 # Tell the moderator (if any) about new uploads from anonymous users. 265 # It can happen for public attachments of publicly editable Wiki's. 266 267 if (!csaTrue $CSA_AUTH_OK && !~ $'cgi.where' wiki && 268 ~ $TNS_EMAIL_MODERATOR *'@'*.*) { 269 csaLoadLib csaEmaillib.rc || csaExit.fault 270 if (csaIsFullPath --exists --quiet \ 271 $CSA_TPL_ROOT/tw-newcontent-email.txt) { 272 tpl_file = tw-newcontent-email.txt 273 } else tpl_file = (--file-root $tw_dstem tw-newcontent-email.txt) 274 275 TNS_EMAIL_TO = $TNS_EMAIL_MODERATOR 276 csaSendMail $tpl_file 277 } 278 279 # Take the client back to the updated attachment list, unless API. 280 # I use also the page name in URL even for public attachments, to 281 # make life easier for rest.map . 282 283 if (!~ $CSA_PGM($#CSA_PGM) metaWeblog.*) { 284 ~ $'cgi.page' () && 285 csaExit.location $CSA_RPC_URI/$CSA_LANG/$'tbl_group.g_uri'/tw-webfile 286 287 csaExit.ok --confirmed \ 288 $CSA_RPC_URI/$CSA_LANG/$'tbl_group.g_uri'/$'tbl_page.p_uri'/wikifile 289 } 290 291 # Next section is entered only if I was called as metaWeblog.newMediaObject 292 293 csaMkTemp tpl.include.tw.rpc 294 295 cat <<'EOF' > $'tpl.include.tw.rpc' || csaExit.fault 0003 cat 296 297 298 url 299 $[tpl.var.tw.url:x] 300 301 302 EOF 303 304 csaExit.ok 305 306 #EOF