1 # ===================================================================== 2 # updatePage: W-TW page writer. 3 # 4 # Copyright (c) 2007-2014 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.page = () 27 cgi.subcat = () 28 cgi.subcat.literal = () 29 cgi.checksum = () 30 cgi.author.uri = () 31 cgi.group.literal = () 32 cgi.page.literal = () 33 cgi.page.uri = () 34 cgi.page.descr = () 35 cgi.reldate = () 36 cgi.expdate = $nil # must not be null. 37 cgi.author = () 38 cgi.tags.tbl = () 39 cgi.tags.xml = () 40 cgi.tags.geo = () 41 cgi.empty = () 42 cgi.link = () 43 cgi.store = () 44 cgi.numeric = () 45 cgi.comments = () 46 cgi.filter = parsewiki # secure default. MUST NOT BE NULL! 47 48 # These may not be null, or clearing them won't work. 49 cgi.allow = $nil # default is read access to all 50 cgi.tags = $nil # may not be null 51 52 new_page = () 53 widget_force = () 54 55 sum_file = /dev/null 56 tmp2 = /dev/null 57 cmd_recent_links = () 58 cmd_recent_pages = () 59 cmd_recent_headlines = () 60 tidy_args = -raw 61 62 tpl.var.checksum = () 63 64 # ===================================================================== 65 # Main program 66 # ===================================================================== 67 68 csaGetArgs POST 69 70 #cp $TNS_CMS_CONTENT /tmp/xxx 71 72 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 73 74 #~ $REMOTE_ADDR 192.168.1.* && { 75 # CSA_RPC2_OK = \ 76 # ''$CSA_LANG/$'cgi.group.literal'/$'cgi.page.literal'^'' 77 # csaExit.ok 78 #} 79 80 . $CSA_ROOT/lib/group-stuff.rc 81 82 # Check the most important arg. 83 ~ $'cgi.page' () && csaExit.fault 1001 84 85 # Page names beginning with tw-* are reserved for TW use, and usually 86 # refer to views that do not correspond to actual pages on disk. 87 88 ~ $'cgi.page' tw-* && csaExit.fault 1009 $'cgi.page' 89 90 tw_pstem = $tw_gstem/$'cgi.page' 91 92 # Check page required metadata. 93 ~ $'cgi.page.uri' () && csaExit.fault 0041 cgi.page.uri 94 ~ $'cgi.page.literal' () && csaExit.fault 0041 cgi.page.literal 95 96 # Enforce the filter specification possibly received in a session cookie 97 # from the client, based on what is currently allowed for this group. 98 # Letting users input raw, unparsed HTML poses a number of security 99 # concerns, especially related to possible malicious javascript code 100 # injection, whereby the default option MUST be to use an intermediate 101 # language (i.e. "parsewiki"). Only those groups which editors can be 102 # clearly identified should allow raw HTML modes. 103 104 # Programmatic clients, such as Blogger 1.0 desktop blogging programs, 105 # always send plain HTML, which is supposed to be well-formed and that 106 # should therefore always undergo the inspection by tidy(1). Furthermore, 107 # such clients rely on authentication data embedded in the payload of the 108 # request and they may not even return the "twfilter" courtesy session 109 # cookie. But even if they did I would disregard it, because I can only 110 # expect raw HTML code from them. Of course I will then check that the 111 # "rawhtml" mode is allowed for the target group, which means that 112 # Blogger programs can only be used on groups that allow raw HTML 113 # editing. Note that "rawhtml" realy means *no GUI whatsoever", which 114 # is why I require the user to enter valid XHTML code, because she 115 # has full control on input. 116 117 ~ $CSA_PGM(1) CSA2 && cgi.filter = rawhtml 118 119 ~ $'cgi.filter' $TNS_ALLOW_GUI || csaExit.fault 1049 120 121 switch ($'cgi.filter') { 122 123 case parsewiki default; # keep as-is. 124 125 case rawhtml 126 if (csaTrue $CSA_XMLISH) { 127 cgi.filter = tidy 128 ~ $CSA_REQ_CHARSET utf-8 && tidy_args = -utf8 129 } else cgi.filter = sed 130 131 case tinymce nicedit ckeditor 132 cgi.filter = sed 133 } 134 135 # Set group and page provisional meta-data as supplied by the user. 136 137 tpl.var.tw.page = $'cgi.page.literal' 138 tpl.var.tw.page.object = $'tpl.var.tw.page' 139 tpl.var.tw.descr = $'cgi.page.descr' 140 tpl.var.tw.tags = $'cgi.tags' 141 tpl.var.tw.author = $'cgi.author' 142 tpl.var.html.title = $'tpl.var.tw.group'/$'tpl.var.tw.page' 143 tpl.var.tw.page.unx = $'cgi.page' 144 145 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 146 147 . $CSA_ROOT/lib/tpl-stuff.rc 148 149 . $CSA_ROOT/lib/group-editor.rc 150 151 # Set Principal Lock Semaphore(s) (PLS). 152 csaLock $tw_gstem/page+dat || csaExit.fault 153 154 # Create target table if it does not yet exist. 155 if (!csaIsFullPath --exists --quiet $tw_gstem/page+dat) { 156 maketable --input \ 157 $CSA_ROOT/lib/page.xrf > $tw_gstem/page+dat || 158 csaExit.fault 0003 maketable 159 } 160 161 # Load page meta-data, if available. 162 163 keysearch $'cgi.page' $tw_gstem/page+dat | 164 csa-tbl2rc --prefix tbl_page. > $tmp1; . $tmp1 165 166 # Set additional attributes if new page. 167 168 if (~ $'tbl_page.k_page' ()) { 169 170 new_page = true 171 172 tbl_page.k_page = $'cgi.page' 173 tbl_page.p_name = $'cgi.page.literal' 174 tbl_page.p_uri = $'cgi.page.uri' 175 tbl_page.p_creau = $'cgi.author' 176 tbl_page.p_ctime = $CSA_TIME_ISO8601 177 tbl_page.p_vtime = 0,$CSA_TIME_ISO # default ranking and 'vtime'. 178 tbl_page.p_npriv = 0 179 tbl_page.p_npub = 0 180 tbl_page.p_ntbk = 0 181 tbl_page.p_ncmt = -1 # comments are disabled by default. 182 tbl_page.p_link = $'cgi.link' 183 184 makeNode $CSA_LANG/$'cgi.group'/$'cgi.page' 185 186 tbl_page.k_node = $node_num 187 188 } else { 189 190 sum_file = $tw_pstem+wki 191 192 # Fix old pages with ctime not already in strict ISO8601 format. 193 ~ $'tbl_page.p_ctime' *[-+]??:?? || tbl_page.p_ctime = \ 194 ``$nl{date -d $'tbl_page.p_ctime' '+%Y-%m-%dT%H:%M:%S%:z'} 195 } 196 197 # Whether the page is new or old, set new editor, description, 198 # catalog data and virtual date, if supplied by the user. 199 200 ~ $'cgi.author' () || tbl_page.p_modau = $'cgi.author' 201 ~ $'cgi.reldate' () || tbl_page.p_vtime = $'cgi.reldate' 202 #~ $'cgi.page.descr' () || tbl_page.p_descr = $'cgi.page.descr' 203 ~ $'cgi.store' :* && cgi.store = $'tbl_page.k_node'$'cgi.store' 204 tbl_page.p_descr = $'cgi.page.descr' 205 tbl_page.p_store = $'cgi.store' 206 tbl_page.p_etime = $'cgi.expdate' 207 208 # Make the page body hidden if empty, and if not already hidden. 209 if (!~ $'cgi.empty' () && !~ $'tbl_page.p_descr' -*) { 210 tbl_page.p_descr = -$'tbl_page.p_descr' 211 } 212 213 switch ($CSA_PGM($#CSA_PGM)) { 214 215 case mt.publishPost 216 # Make page visible if called for this very purpose. 217 tbl_page.p_descr = ``(){echo -n $'tbl_page.p_descr' | sed 's,^-,,'} 218 219 case blogger.deletePost 220 # Logically deleted pages MUST have their description set to 221 # a single hyphen, with no other text forrowing it, or a number 222 # of other TW views will break. 223 tbl_page.p_descr = - 224 } 225 226 tbl_page.p_modip = $REMOTE_ADDR 227 tbl_page.p_mtime = $CSA_TIME_ISO8601 228 229 if (!~ $CSA_PGM($#CSA_PGM) mt.publishPost) { 230 tbl_page.p_allow = $'cgi.allow' 231 tbl_page.p_tags = $'cgi.tags' 232 tbl_page.p_link = $'cgi.link' 233 } 234 235 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 236 237 # Set template vars to their final values. 238 tpl.var.tw.page = $'tbl_page.p_name' 239 tpl.var.tw.page.object = $'tpl.var.tw.page' 240 tpl.var.tw.descr = $'tbl_page.p_descr' 241 tpl.var.tw.author = $'tbl_page.p_modau' 242 tpl.var.tw.page.store = $'tbl_page.p_store' 243 tpl.var.tw.page.ping.count = $'tbl_page.p_ntbk' 244 tpl.var.tw.page.att.count.priv = $'tbl_page.p_npriv' 245 * = ``(' ':T+-){echo -n $'tbl_page.p_mtime'} 246 tpl.var.tw.chgdate = $1-$2-$3 247 tpl.var.tw.chgtime = $4:$5 248 tpl.var.html.title = $'tpl.var.tw.group'/$'tpl.var.tw.page' 249 tpl.var.tw.page.unx = $'cgi.page' 250 251 tpl.var.tw.node = $'tbl_page.k_node' 252 253 # Page description ultimately defaults to group/page name in templates. 254 ~ $'tpl.var.tw.descr' () && tpl.var.tw.descr = $'tpl.var.html.title' 255 256 if (!~ $CSA_PGM($#CSA_PGM) mt.publishPost) { 257 ~ $'tbl_page.p_descr' () && tbl_page.p_descr = $'tpl.var.tw.descr' 258 } 259 260 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 261 262 # Policy checks should always come after the inclusion of group 263 # (and possibly also page) meta-data. 264 265 ~ ,$TNS_AUTH_GRP, *,editor,* || csaExit.needauth 266 267 csaSum --file $sum_file 268 269 # This is already contained in CSA_RWA but setting it again here 270 # wont' hurt and it makes things more clear. 271 tpl.var.checksum = $CSA_RESULT 272 273 # Make sure the requested record hasn't changed in the meantime. 274 # (old logic, leave as comment for a while). 275 #if (csaIsInteractive && !~ $CSA_RESULT $'cgi.checksum') { 276 # CSA_EXIT_SCRIPT = ($CSA_EXIT_SCRIPT back) 277 # #~ $'cgi.checksum' () && csaExit.fault 1026 278 # csaExit.fault 0026 279 #} 280 281 # Checksum-based race tests are done only if the client provided a 282 # previous checksum value. If she didn't, then this test is skipped, 283 # assuming that the client does not want this test to be done. This is 284 # necessary also to make the "blogger.editPost" Blogger API method work, 285 # as the API does not provide any checksum argument. 286 287 if (!~ $'cgi.checksum' ()) { 288 289 csaSum --file $sum_file 290 291 # This is already contained in CSA_RWA but setting it again here 292 # wont' hurt and it makes things more clear. 293 tpl.var.checksum = $CSA_RESULT 294 295 # Make sure the requested record hasn't changed in the meantime. 296 if (!~ $'tpl.var.checksum' $'cgi.checksum') { 297 csaIsInteractive && CSA_EXIT_SCRIPT = ($CSA_EXIT_SCRIPT back) 298 csaExit.fault 0026 299 } 300 } 301 302 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 303 304 # Make sure static views are always built using interactive-type 305 # CSA URLs if we are in RPC mode. 306 ~ $CSA_PGM(1) CSA2 && CSA_RPC_URI = $CSA_RPC_URI/I 307 308 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 309 310 if (!csaIsConfirmed) { 311 312 if (~ $'cgi.filter' tidy) { 313 314 # Temporarily comment-out CPIs, or tidy(1) will complain if 315 # they fall outside proper #CDATA sections. 316 317 sed 's/\((:[^)]\+:)\)//g' $TNS_CMS_CONTENT > $tmp1 || 318 csaExit.fault 0003 sed 319 320 csaSystem --return tidy -config /dev/null \ 321 -indent -quiet -wrap 72 -xml -asxhtml $tidy_args $tmp1 322 323 if (!~ $CSA_STATUS 0) { 324 echo -- ------------------------------------ >> $CSA_SYSERR 325 cat -n $CSA_SYSOUT >> $CSA_SYSERR 326 327 # if (csaIsFullPath --exists --quiet \ 328 # $CSA_TPL_ROOT/tw-nav-back.txt) { 329 # # custom "back" action template. 330 # tpl.include.nav.next = $CSA_TPL_ROOT/tw-nav-back.txt 331 # } else { 332 # # default "back" action template. 333 # tpl.include.nav.next = $tw_dstem/tw-nav-back.txt 334 # } 335 336 tpl.include.tw.msg = $CSA_SYSERR 337 338 csaExit.fault --back 1008 339 } 340 341 # tidy(1) insists on stripping blanks just after a closing XML and 342 # just before line breaks, which is often inappropriate except in 343 # a few cases (add more exceptions as needed). This kludge is 344 # probably far from perfect, so watch out. Note that the very 345 # first line restores CPIs that were previously commented out. 346 347 sed 's//\1/g 348 s/\(<\/[^\/>]\+>\)\([^.,:;?!''`)]\)/\1 \2/g 349 s/\([^>]\)$/\1 / 350 s/ *\( \) */\1/g 351 s/ *$/ / 352 s/\([("]\) *$/\1/' $TNS_CMS_CONTENT > $tmp1 353 354 } else { 355 356 if (~ $'cgi.filter' parsewiki) { 357 $TNS_CMD_WIKI2HTML $TNS_CMS_CONTENT > $tmp1 358 } else sed -f $CSA_ROOT/lib/xmlbreak.sed $TNS_CMS_CONTENT > $tmp1 359 } 360 361 tpl.include.tw.page = $tmp1 362 363 if (csaIsFullPath --exists --quiet \ 364 $CSA_TPL_ROOT/tw-edit-page-confirm.txt) { 365 # custom editing confirmation template. 366 tpl.include.html.body = $CSA_TPL_ROOT/tw-edit-page-confirm.txt 367 } else { 368 # default editing confirmation template. 369 tpl.include.html.body = $tw_dstem/tw-edit-page-confirm.txt 370 } 371 372 # Make sure no-replace mode is used on the page body by prepending 373 # its name with '-', or any user-supplied CSA tags in the text will 374 # be happily parsed by _envtoxml() !! 375 376 tpl.include.tw.page = -$'tpl.include.tw.page':c 377 378 # PRG confirmation page must be regarded as a view. 379 tpl.if.tw.ispage = '(::DEL:)' 380 tpl.fi.tw.ispage = '(:DEL::)' 381 tpl.if.tw.printable = '(::DEL:)' 382 tpl.fi.tw.printable = '(:DEL::)' 383 tpl.if.tw.isview = () 384 tpl.fi.tw.isview = () 385 386 csaExit.ok $tpl_file 387 } 388 389 # Create the target stuff as necessary. 390 391 if (!csaIsFullPath --exists --quiet $tw_pstem+wki) { 392 393 # Let's be very careful about the public upload directory, as it is 394 # located in an area wich is accessible to the webmaster through FTP. 395 # In theory, the user could remove the directory and re-create it with 396 # different permissions (at least while the directory is still empty). 397 # If he did, then our next touch(1) will fail and we will abort the 398 # operation with complaints. A user can remove a non-owned directory from 399 # an owned directory tree only if the directory is empty, so we touch a 400 # placeholder into it first, just in case. All this should give us enough 401 # security. 402 403 test -d $CSA_DOCROOT/$CSA_LANG/$TNS_ATTACH_PUBDIR/$'cgi.group' || 404 csaExit.fault 0008 $CSA_DOCROOT/$CSA_LANG/$TNS_ATTACH_PUBDIR/$'cgi.group' 405 406 touch $CSA_DOCROOT/$CSA_LANG/$TNS_ATTACH_PUBDIR/$'cgi.group'/.touch || 407 csaExit.fault 0009 \ 408 $CSA_DOCROOT/$CSA_LANG/$TNS_ATTACH_PUBDIR/$'cgi.group'/.touch 409 new_page = true 410 } 411 412 # Bail-out with an error if this is supposed to be a new post 413 # over RPC2 but the relevant page already exists. 414 415 ~ $CSA_PGM(1) CSA2 && ~ $CSA_PGM($#CSA_PGM) *.newPost && 416 !csaTrue $new_page && csaExit.fault 1022 $'cgi.page.literal' 417 418 # Now create any other loosely associated files if necessary. 419 # Tidy(1) gives us much more than a cp(1) here, and is still 420 # only one process. 421 422 csaOpen --fast --relaxed $tw_pstem+wki || csaExit.fault 423 424 tmp1 = $CSA_RESULT 425 426 if (~ $'cgi.filter' tidy) { 427 428 # Temporarily comment-out CPIs, or tidy(1) will complain 429 # if they fall outside proper #CDATA sections. 430 431 sed 's/\((:[^)]\+:)\)//g' $TNS_CMS_CONTENT > $tmp1 || 432 csaExit.fault 0003 sed 433 434 csaSystem --return tidy -config /dev/null \ 435 -indent -quiet -wrap 72 -xml -asxhtml $tidy_args $tmp1 436 437 if (!~ $CSA_STATUS 0) { 438 echo -- ------------------------------------ >> $CSA_SYSERR 439 cat -n $CSA_SYSOUT >> $CSA_SYSERR 440 tpl.include.tw.msg = $CSA_SYSERR 441 csaExit.fault --back 1008 442 } 443 444 # tidy(1) insists on stripping blanks just after a closing XML and 445 # just before line breaks, which is often inappropriate except in 446 # a few cases (add more exceptions as needed). This kludge is 447 # probably far from perfect, so watch out. Note that the very 448 # first line restores CPIs that were previously commented out. 449 450 sed 's//\1/g 451 s/\(<\/[^\/>]\+>\)\([^.,:;?!''`)]\)/\1 \2/g 452 s/\([^>]\)$/\1 / 453 s/ *\( \) */\1/g 454 s/ *$/ / 455 s/\([("]\) *$/\1/' $CSA_SYSOUT > $tmp1 456 457 } else { 458 459 if (~ $'cgi.filter' parsewiki) { 460 csaOpen --fast --relaxed $tw_pstem+pwk || csaExit.fault 461 cp $TNS_CMS_CONTENT $CSA_RESULT || csaExit.fault 0003 cp 462 $TNS_CMD_WIKI2HTML $TNS_CMS_CONTENT > $tmp1 463 } else sed -f $CSA_ROOT/lib/xmlbreak.sed $TNS_CMS_CONTENT > $tmp1 464 } 465 466 # Disregard empty page body if called just to publish. 467 ~ $CSA_PGM($#CSA_PGM) mt.publishPost && cp $tw_pstem+wki $tmp1 468 469 TNS_NEW_CONTENT = $tmp1 470 471 #if (~ $REMOTE_ADDR 192.168.1.2) { 472 # cp $TNS_NEW_CONTENT /tmp/xxx 473 # csaExit.env 474 #} 475 476 # Update the page meta-data table. 477 478 csaOpen --fast $tw_gstem/page+dat || csaExit.fault 479 tmp_pages = $CSA_RESULT # needed several times below. 480 481 csaMkTemp tmp2 482 483 envtotable --match '^tbl_page__2e[a-z]' \ 484 --strip-names '^tbl_page__2e' --output $tmp2 || 485 csaExit.fault 0003 envtotable 486 487 tabletolist --no-header --no-footer --input $tmp2 | 488 sed -e 1i$eoh^Column$tab$eoh^Table$tab$eoh^Value$tab$eoh^Action \ 489 -e s/$tab/$tab^page$tab/ -e 's/$/'$tab^Insert/ | 490 constraint $CSA_ROOT/lib/schema.dat >/dev/null 491 492 csaStatus || csaExit.fault 0080 # database exception detected 493 494 updtable --key-columns k_page $tmp2 < $tw_gstem/page+dat | 495 sorttable > $tmp_pages 496 497 csaStatus || csaExit.fault 0003 updtable/sorttable 498 499 if (!~ $'cgi.tags' $nil) { 500 # Create the tag table if it does not yet exist, then insert 501 # the new tags into it. 502 if (!csaIsFullPath --exists --quiet $tw_gstem/tag+dat) { 503 maketable --input \ 504 $CSA_ROOT/lib/tag.xrf > $tw_gstem/tag+dat || 505 csaExit.fault 0003 maketable 506 } 507 508 csaOpen --fast --relaxed $tw_gstem/tag+dat || csaExit.fault 509 tmp1 = $CSA_RESULT 510 511 { 512 # Remove all tags previously associated with the current 513 # page as they are to be replaced with the new ones entered. 514 515 awktable -H -i $tw_gstem/tag+dat -vp_'='$'cgi.page' -- \ 516 '$k_page == p_ {next}{print}' 517 518 # Exclude page from updated tag table if hidden/deleted. 519 if (!~ $'cgi.tags' () && !~ $'tbl_page.p_descr' -*) { 520 521 # Handle as many '%s' printf(1) args as needed. This kludge 522 # is necessary because updatePage.awk does not always have 523 # a way to know the actual page name in addition to its 524 # unixified version. This can happen whenever an editor 525 # (especially if using an API client) references the target 526 # page by its unixified name. So the only safe place where 527 # the actual page name can be placed inside the cgi.tags.tbl 528 # fragment is here, not in updatePage.awk. 529 530 tns1 = () 531 * = `{echo $'cgi.tags.tbl'} 532 while (!~ $1 ()) { 533 ~ $1 %s && tns1 = ($tns1 $'tbl_page.p_name') 534 shift 535 } 536 printf $'cgi.tags.tbl' $tns1 537 } 538 539 } | sorttable -u > $tmp1 540 541 csaStatus || csaExit.fault 0003 sorttable:tag-cloud 542 543 # Update the tag-cloud static view. 544 545 csaTrue $CSA_AUDIT && 546 csaTrapFile $tw_gstem/RCS/^((tag+dat tag-cloud+xml)^,v) 547 548 csaOpen --fast --relaxed $tw_gstem/tag-cloud+xml || csaExit.fault 549 tmp1 = $CSA_RESULT 550 csaAwkCmd groupTagCloud.awk 551 getcolumn --input $tmp_pages p_^(name tags descr) | 552 $CSA_RESULT > $tmp1 553 554 csaStatus || csaExit.fault 0003 getcolumn/AWK:tag-cloud 555 556 # Update the page keywords static object. 557 558 csaTrapFile $tw_gstem/RCS/$'cgi.page'-meta+xml,v 559 560 csaOpen --fast --relaxed $tw_pstem-meta+xml || csaExit.fault 561 tmp1 = $CSA_RESULT 562 563 echo '' > $tmp1 565 566 # The geo.placename attribute is currently hard-wired to page name. 567 echo '' >> $tmp1 569 570 if (~ $'cgi.tags.xml' *[a-z]*) { 571 572 #echo '' >> $tmp1 574 575 # keywords should rather appear in this other way. 576 { 577 echo -n '' 580 } >> $tmp1 581 } 582 583 # Note that no special checks are done on the well-formedness of Geo 584 # values. If any more checks are desired they will have to be done 585 # in updatePage.awk . 586 587 if (!~ $'cgi.tags.geo'(2) ()) { 588 589 echo '' >> $tmp1 591 echo '' >> $tmp1 593 594 ~ $'cgi.tags.geo'(3) () || 595 echo '' >> $tmp1 597 } 598 599 ~ $'tpl.var.tw.author' () || 600 echo '' >> $tmp1 602 603 csaStatus || csaExit.fault 0009 $tmp1 604 } 605 606 if (~ $'cgi.subcat' [a-z]*) { 607 608 csaTrapFile $tw_gstem/RCS/cat-cloud+xml,v 609 610 # Update the subcat-cloud static view. 611 csaOpen --fast --relaxed $tw_gstem/cat-cloud+xml || csaExit.fault 612 tmp1 = $CSA_RESULT 613 csaAwkCmd groupCatCloud.awk 614 getcolumn --input $tmp_pages p_^(name descr) | 615 $CSA_RESULT > $tmp1 616 617 csaStatus || csaExit.fault 0003 getcolumn/AWK:cat-cloud 618 } 619 620 # Update the recent-links static view. Note that this view is 621 # unrestricted so we need to be conservative and exclude any 622 # hidden/redirected pages from it. I do not exclude restricted pages 623 # though, as the idea is that page titles and their short descriptions 624 # can always be publicly accessible, and that only the page body should 625 # abide by those restrictions. After all, being page comments always 626 # publicly readable there is always a partial exposure of stuff that may 627 # relate to restricted content, so trying to be absolutely water-tight 628 # in only a few places does not make much sense. 629 630 csaOpen --fast --relaxed $tw_gstem/recent-links+xml || csaExit.fault 631 tmp1 = $CSA_RESULT 632 csaAwkCmd groupRecentLinks.awk 633 cmd_recent_links = $CSA_RESULT 634 awktable -i $tmp_pages -- 'BEGIN{print "\001p_name\t\001p_ctime" \ 635 "\t\001p_modip\t\001p_creau\t\001p_descr\t\001p_uri\t\001p_vtime" 636 } 637 # Handle page expiration dates, accounting for older 638 # versions of page+dat which may lack that field. 639 p_etime/=1 { 640 # Set default expiration date. 641 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59" 642 if ("'$CSA_TIME_ISO'" >= $p_etime && \ 643 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr 644 } 645 # Handle explicit exclusions from static views first. 646 $p_descr ~ /^ *!/ {next} 647 # Exclude hidden/redirected pages from output. Redirections which 648 # are NOT to be excluded must be *immediately* preceeded by "+" . 649 {sub(/\+\(:redirect /,"(:+redirect ",$p_descr)} 650 $p_descr !~ /(^ *-|\(:redirect )/ { 651 sub(/\(:\+redirect /,"(:redirect ",$p_descr) 652 print $p_name,$p_ctime,$p_modip,$p_creau,$p_descr,$p_uri,$p_vtime 653 }' | sorttable -r $TNS_RECENT_LINKS_PROP(1) | 654 head -n $TNS_RECENT_LINKS_PROP(2) | $cmd_recent_links > $tmp1 655 656 # It looks like that GNU sort(1) traps SIGPIPE, and returns 2 on both 657 # SIGPIPE and EPIPE. This does not stand for an error, because it 658 # is due to head(1) closing the pipeline after the specified number 659 # of lines have been read. Furthermore, it does not always happen, 660 # possibly depending on the size of the input data, on the machine 661 # load and on other momentary factors. Ignoring error code 2 seems 662 # to fix that. 663 664 csaStatus sigpipe 2 || csaExit.fault 0003 awktable/sorttable/head/AWK 665 666 # Update the recent-pages static view. Note that this view is 667 # unrestricted so we need to be conservative and exclude any 668 # hidden/redirected/restricted pages from it. Also note how, 669 # unlike the recent-links view, I use 'vtime' here (as well 670 # as for the 'recent-headlines' view), as I think it makes more 671 # sense here. 672 673 csaOpen --fast --relaxed $tw_gstem/recent-pages+xml || csaExit.fault 674 tmp1 = $CSA_RESULT 675 csaAwkCmd groupRecentPages.awk 676 cmd_recent_pages = $CSA_RESULT 677 TNS_FILTER_ARGS = groupRecentPages 678 awktable -i $tmp_pages -- 'BEGIN { 679 print "\001k_page\t\001p_vtime\t\001p_name" \ 680 "\t\001p_modau\t\001p_uri\t\001p_descr\t\001p_etime" 681 } 682 $p_vtime !~ /^[0-9],/ {$p_vtime = "0," $p_vtime} # default ranking 683 684 # Safety-measure for backward compatibility. 685 !p_etime {p_etime = 99} 686 687 # Handle page expiration date. 688 { 689 # Set default expiration date. 690 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59" 691 if ("'$CSA_TIME_ISO'" >= $p_etime && \ 692 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr 693 } 694 # Handle explicit exclusions from static views first. 695 $p_descr ~ /^ *!/ {next} 696 # Exclude restricted/hidden/redirected pages from output. Redirections 697 # which are NOT to be excluded must be *immediately* preceeded by "+" . 698 {sub(/\+\(:redirect /,"(:+redirect ",$p_descr)} 699 $p_allow=="" && $p_descr !~ /(^ *-|\(:redirect )/ { 700 sub(/\(:\+redirect /,"(:redirect ",$p_descr) 701 print $k_page,$p_vtime,$p_name,$p_modau,$p_uri,$p_descr,$p_etime 702 }' | sorttable -r p_vtime | head -n $TNS_RECENT_PAGES_NUM | 703 $cmd_recent_pages > $tmp1 704 705 csaStatus sigpipe 2 || csaExit.fault 0003 getcolumn/sorttable/head/AWK 706 707 # Update the recent-headlines static view. 708 csaOpen --fast --relaxed $tw_gstem/recent-headlines+xml || csaExit.fault 709 tmp1 = $CSA_RESULT 710 csaAwkCmd groupRecentHeadlines.awk 711 cmd_recent_headlines = $CSA_RESULT 712 TNS_FILTER_ARGS = groupRecentHeadlines 713 awktable -i $tmp_pages -- 'BEGIN{print "\001k_page\t\001p_vtime" \ 714 "\t\001p_name\t\001p_modau\t\001p_uri\t\001p_descr\t\001p_etime"} 715 716 # Safety-measure for backward compatibility. 717 !p_etime {p_etime = 99} 718 719 # Handle page expiration dates. 720 { 721 # Set default expiration date. 722 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59" 723 if ("'$CSA_TIME_ISO'" >= $p_etime && \ 724 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr 725 } 726 # Handle explicit exclusions from static views first. 727 $p_descr ~ /^ *!/ {next} 728 # Exclude hidden/redirected pages. Redirections which are 729 # NOT to be excluded must be *immediately* preceeded by "+" . 730 {sub(/\+\(:redirect /,"(:+redirect ",$p_descr)} 731 $p_descr ~ /(^ *-|\(:redirect )/ {next} 732 {sub(/\(:\+redirect /,"(:redirect ",$p_descr)} 733 $p_vtime !~ /^[0-9],/ {$p_vtime = "0," $p_vtime} # default ranking 734 {print $k_page,$p_vtime,$p_name,$p_modau,$p_uri,$p_descr,$p_etime}' | 735 sorttable -r p_vtime | head -n $TNS_RECENT_HEADLINES_NUM | 736 $cmd_recent_headlines | $TNS_VIEW_FILTER > $tmp1 737 738 csaStatus sigpipe 2 || csaExit.fault 0003 getcolumn/sorttable/head/AWK 739 740 # Update the recent-changes static view. 741 csaOpen --fast --relaxed $tw_gstem/recent-changes+xml || csaExit.fault 742 tmp1 = $CSA_RESULT 743 csaAwkCmd groupRecentChanges.awk 744 awktable -i $tmp_pages -- 'BEGIN{print \ 745 "\001p_uri\t\001p_name\t\001p_mtime\t\001p_modip\t\001p_descr"} 746 # Handle page expiration dates, accounting for older 747 # versions of page+dat which may lack that field. 748 p_etime/=1 { 749 # Set default expiration date. 750 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59" 751 if ("'$CSA_TIME_ISO'" >= $p_etime && \ 752 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr 753 } 754 # Only hidden pages are excluded from this particular view. 755 # Strip any redirection URL from page visible description. 756 {sub(/ *\+\(:redirect .*/,_NULL,$p_descr)} 757 # Remove any explicit exclusion directive. 758 {sub(/^ *! */,"",$p_descr)} 759 {print $p_uri,$p_name,$p_mtime,$p_modip,$p_descr}' | 760 sorttable -r p_mtime | head -n $TNS_RECENT_CHANGES_NUM | 761 $CSA_RESULT > $tmp1 762 763 csaStatus sigpipe 2 || csaExit.fault 0003 getcolumn/sorttable/head/AWK 764 765 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 766 767 # In case the versioning subdir does not exist yet (no TPC needed here). 768 mkdir -p $tw_gstem/RCS || csaExit.fault 0003 mkdir 769 770 # Update the -recent-pages static view, if applicable. 771 if (~ $'cgi.subcat' [a-z]*) { 772 ~ $tmp2 /dev/null && csaMkTemp tmp2 # may still be undefined. 773 keysearch --partial $'cgi.subcat'. $tmp_pages | 774 awktable -- 'BEGIN { 775 print "\001k_page\t\001p_vtime\t\001p_name" \ 776 "\t\001p_modau\t\001p_uri\t\001p_descr\t\001p_etime" 777 } 778 779 # Safety-measure for backward compatibility. 780 !p_etime {p_etime = 99} 781 782 # Handle page expiration dates, accounting for older 783 # versions of page+dat which may lack that field. 784 { 785 # Set default expiration date. 786 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59" 787 if ("'$CSA_TIME_ISO'" >= $p_etime && \ 788 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr 789 } 790 # Handle explicit exclusions from static views first. 791 $p_descr ~ /^ *!/ {next} 792 # Exclude restricted/hidden/redirected pages from output. Redirections 793 # which are NOT to be excluded must be *immediately* preceeded by "+" . 794 {sub(/\+\(:redirect /,"(:+redirect ",$p_descr)} 795 $p_allow!="" || $p_descr ~ /(^ *-|\(:redirect )/ {next} 796 {sub(/\(:\+redirect /,"(:redirect ",$p_descr)} 797 $p_vtime !~ /^[0-9],/ {$p_vtime = "0," $p_vtime} # default ranking 798 {print $k_page,$p_vtime,$p_name,$p_modau,$p_uri,$p_descr,$p_etime}' | 799 sorttable -r p_vtime > $tmp2 800 csaStatus || csaExit.fault 0003 keysearch/awktable/sorttable 801 802 csaOpen --fast --relaxed \ 803 $tw_gstem/$'cgi.subcat'-recent-pages+xml || csaExit.fault 804 tmp1 = $CSA_RESULT 805 806 head -n $TNS_RECENT_PAGES_NUM $tmp2 | $cmd_recent_pages > $tmp1 807 csaStatus || csaExit.fault 0003 head/AWK 808 csaTrapFile $tw_gstem/RCS/$'cgi.subcat'-recent-pages+xml,v 809 810 # Update the -recent-headlines static view. 811 csaOpen --fast --relaxed \ 812 $tw_gstem/$'cgi.subcat'-recent-headlines+xml || csaExit.fault 813 tmp1 = $CSA_RESULT 814 815 head -n $TNS_RECENT_HEADLINES_NUM $tmp2 | $cmd_recent_headlines > $tmp1 816 csaStatus || csaExit.fault 0003 head/AWK 817 csaTrapFile $tw_gstem/RCS/$'cgi.subcat'-recent-headlines+xml,v 818 819 # Update the -recent-links static view. 820 csaOpen --fast --relaxed \ 821 $tw_gstem/$'cgi.subcat'-recent-links+xml || csaExit.fault 822 tmp1 = $CSA_RESULT 823 824 keysearch --partial $'cgi.subcat'. $tmp_pages | 825 awktable -- 'BEGIN{print "\001p_name\t\001p_ctime" \ 826 "\t\001p_modip\t\001p_creau\t\001p_descr\t\001p_uri\t\001p_vtime" 827 } 828 # Handle page expiration dates, accounting for older 829 # versions of page+dat which may lack that field. 830 p_etime/=1 { 831 # Set default expiration date. 832 if ($p_etime == "") $p_etime = "9999-12-31 23:59:59" 833 if ("'$CSA_TIME_ISO'" >= $p_etime && \ 834 $p_descr !~ /^ *-/) $p_descr = "-" $p_descr 835 } 836 # Handle explicit exclusions from static views first. 837 $p_descr ~ /^ *!/ {next} 838 # Exclude hidden/redirected pages. Redirections which are 839 # NOT to be excluded must be *immediately* preceeded by "+" . 840 {sub(/\+\(:redirect /,"(:+redirect ",$p_descr)} 841 $p_descr !~ /(^ *-|\(:redirect )/ { 842 {sub(/\(:\+redirect /,"(:redirect ",$p_descr)} 843 print $p_name,$p_ctime,$p_modip,$p_creau,$p_descr,$p_uri,$p_vtime 844 }' | sorttable -r $TNS_RECENT_LINKS_PROP(1) | 845 head -n $TNS_RECENT_LINKS_PROP(2) | $cmd_recent_links > $tmp1 846 csaStatus sigpipe 2 || csaExit.fault 0003 awktable/sorttable/head/AWK 847 csaTrapFile $tw_gstem/RCS/$'cgi.subcat'-recent-links+xml,v 848 } 849 850 # Create page widgets if necessary. 851 852 # Set target year/month for the calendar widget. 853 * = ``(-,){echo -n $'tbl_page.p_vtime'} 854 ~ $#* 4 && shift # strip ranking 855 gencal_month = $* 856 857 gencal_xml = $tw_gstem/cal-$gencal_month(1)^-$gencal_month(2)^+xml 858 genarch_xml = $tw_gstem/year-links+xml 859 gencat_xml = $tw_gstem/cat-links+xml 860 gencat_mt = $tw_gstem/cat-mt+xml 861 gencat_mw = $tw_gstem/cat-mw+xml 862 863 gencal_rcs = $tw_gstem/RCS/cal-$gencal_month(1)^-$gencal_month(2)^+xml,v 864 genarch_rcs = $tw_gstem/RCS/year-links+xml,v 865 gencat_rcs = $tw_gstem/RCS/cat-links+xml,v 866 gencat_mt_rcs = $tw_gstem/RCS/cat-mt+xml,v 867 gencat_mw_rcs = $tw_gstem/RCS/cat-mw+xml,v 868 869 widget_input = $tmp_pages 870 871 widget_force = 1 872 873 . $CSA_ROOT/lib/widgets.rc 874 875 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 876 877 # Register pingback data, if any. PLS/TPC are not only unnecessary 878 # here, but the MUST NOT be done, not to interfere with the lengthy 879 # processing of the 'queue-run pingback' processor. Note that these 880 # file contents are written partly by _userproc(_O_REQUEST) and 881 # partly by _userproc(_O_RESPONSE), so they must be moved to their 882 # final destinations only upon committing, i.e. after calling also 883 # _userproc(_O_RESPONSE). 884 885 # Queue pingbacks only if the page is publicly readable, and only 886 # if allowed. Note: if the pingback file was not created because no 887 # pingbacks were detected in the edited content, then the commit &&' 888 # chain will not complete because of such errors! That's why said file 889 # must be created with 'csaMkTemp --file'. 890 891 if (!~ $'tbl_page.p_descr' -* && ~ $'tbl_page.p_allow' $nil) { 892 893 # By testing here, pingbacks can be enabled/disabled on a per-group 894 # basis rather than globally. 895 csaTrue $TNS_PING_XREF && csaOnCommit --caller \ 896 cat' '$TNS_PINGBACK_QUEUE' >> '$CSA_ROOT/var/tw-pingback-queue 897 } 898 899 # Unconditionally queue the rebuilding of sitemaps, as something was 900 # modified/deleted. This is currently no longer done since according 901 # to TypeWriter's philosophy, all publicly readable pages of a TW 902 # site are also publicly accessible through the relevant RESTful URLs. 903 # Should I provide explicit site maps in the future, I will most likely 904 # opt for more efficient incremental site maps based on Recent Changes. 905 # 906 #csaOnCommit --caller echo' '$tw_gstem' >> '$CSA_ROOT/var/tw-updates 907 908 # If parsewiki(1) mode is in effect, then both 'page+pwk' and 'page+wki' 909 # will normally end up having the same modification time from the point 910 # of view of test(1). Since this assumption may be used in the future 911 # by 'editPage' to assess whether 'page+wki' is newer than 'page+pwk', 912 # and since in case of a high machine load this may not be the case, 913 # I prefer to enforce such condition. 914 915 if (~ $'cgi.filter' parsewiki && !~ $CSA_PGM(1) CSA2) { 916 csaOnCommit touch' -m '$tw_pstem+pwk' '$tw_pstem+wki 917 } 918 919 # Toggle comments on, off or leave them unchanged for this page. 920 921 # Comments are enabled by default for new pages if appropriate. 922 csaTrue $new_page && csaTrue $TNS_CMT_ENABLE && cgi.comments = true 923 924 switch ($'cgi.comments') { 925 926 case true 927 # Set Principal Lock Semaphore(s) (PLS) (see also cmtPost). 928 csaLock $tw_gstem/recent-comment+dat || csaExit.fault 929 930 # Re-enable the previous comment table if it exists. 931 if (csaIsFullPath --exists --quiet $tw_gstem/.$'cgi.page'+cmt) { 932 csaOnCommit \ 933 mv' '$tw_gstem/.$'cgi.page'+cmt' '$tw_gstem/$'cgi.page'+cmt 934 } else { 935 # Create a new comment table if it does not yet exist. 936 if (!csaIsFullPath --exists --quiet $tw_gstem/$'cgi.page'+cmt) { 937 csaOpen --fast --relaxed $tw_gstem/$'cgi.page'+cmt || 938 csaExit.fault 939 maketable --input \ 940 $CSA_ROOT/lib/comment.xrf > $CSA_RESULT || 941 csaExit.fault 0003 maketable 942 } 943 } 944 945 case false 946 # Set Principal Lock Semaphore(s) (PLS) (see also cmtPost). 947 csaLock $tw_gstem/recent-comment+dat || csaExit.fault 948 949 # Make any previous table hidden. 950 if (csaIsFullPath --exists --quiet $tw_gstem/$'cgi.page'+cmt) { 951 csaOnCommit \ 952 mv' '$tw_gstem/$'cgi.page'+cmt' '$tw_gstem/.$'cgi.page'+cmt 953 } else { 954 # Create a new hidden comment table if it does not yet exist. 955 if (!csaIsFullPath --exists --quiet $tw_gstem/.$'cgi.page'+cmt) { 956 csaOpen --fast --relaxed $tw_gstem/.$'cgi.page'+cmt || 957 csaExit.fault 958 maketable --input \ 959 $CSA_ROOT/lib/comment.xrf > $CSA_RESULT || 960 csaExit.fault 0003 maketable 961 } 962 } 963 } 964 965 if (csaTrue $CSA_AUDIT) { 966 967 # Prevent useless versioning of most ancillary files. 968 csaTrapFile $tw_gstem/RCS/^((page+dat (recent-links 969 recent-pages recent-changes recent-headlines)^+xml)^,v) 970 } 971 972 # Set new author last, and only if available. 973 #~ $'cgi.author' - || ~ $'cgi.author' () || 974 # csaCookie.set twauthor $'cgi.author.uri' \ 975 # --expires ``($nl){date -u -d now+6month +'%a, %d-%b-%Y %H:%M:%S GMT'} 976 977 # Handle page update notifications, i.e. dispatch them to the relevant 978 # batch processor as quickly as possible, not to engage this already 979 # slowish program into even lengthier operations. 980 981 # Let's be conservative regarding what may or may not be notified. 982 if (!~ $'tbl_page.p_descr' -* && ~ $'tbl_page.p_allow' $nil) { 983 984 ~ $tmp2 /dev/null && csaMkTemp tmp2 # may still be undefined. 985 986 awktable -i $TNS_USER_TABLE -- 'BEGIN{ rc_=1 } 987 { sub(/\|.*/,"",$u_other) } # retain only 1st subfield. 988 # Note: deleted accounts are implicitly excluded by virtue of 989 # having u_other set to "x". 990 "," $u_other "," ~ /,'$'tbl_page.k_node','/ { 991 # Extract notification gateway parameters. 992 # Only e-mail notifications are supported right now. 993 gw_ = $u_other; sub(/,.*/,"",gw_) 994 if (gw_ == "") gw_ = "m" 995 printf("%s^%s ",gw_,$u_email); rc_=0 996 } 997 END { printf ("\n"); exit(rc_) }' > $tmp2 998 999 if (~ $status 0) { 1000 1001 csaLoadLib csaEmaillib.rc || csaExit.fault 1002 1003 emaillib_loaded = 1 1004 1005 if (csaIsFullPath --exists --quiet \ 1006 $CSA_TPL_ROOT/tw-page-updated-email.txt) { 1007 tpl_file = tw-page-updated-email.txt 1008 } else tpl_file = (--file-root $tw_dstem tw-page-updated-email.txt) 1009 1010 # By overriding the CSA_CMD_SENDMAIL_ variable can trick the 1011 # 'csaSendMail' function into not sending mail, but rather acting 1012 # as a generic form processor. 1013 1014 CSA_CMD_SENDMAIL_ = 'cat >> '$tmp2 { csaSendMail $tpl_file } 1015 1016 csaOnCommit cp $tmp2 $CSA_ROOT/var/tw-page-updated-$'tbl_page.k_node' 1017 } 1018 } 1019 1020 # Send the client the appropriate response message if we are in RPC2 mode. 1021 1022 if (~ $CSA_PGM(1) CSA2) { 1023 1024 # Return post-id if new post. 1025 if (~ $CSA_PGM($#CSA_PGM) *.newPost) { 1026 if (~ $'cgi.numeric' ()) { 1027 CSA_RPC2_OK = \ 1028 ''$CSA_LANG/$'cgi.group'/$'cgi.page'^'' 1029 } else CSA_RPC2_OK = ''$'tbl_page.k_node'^'' 1030 } 1031 1032 csaExit.ok 1033 } 1034 1035 # Tell the moderator (if any) about new content from *anonymous* users. 1036 # It can happen for publicly editable Wiki's. 1037 if (!csaTrue $CSA_AUTH_OK && ~ $TNS_EMAIL_MODERATOR *'@'*.*) { 1038 csaTrue emaillib_loaded || csaLoadLib csaEmaillib.rc || csaExit.fault 1039 if (csaIsFullPath --exists --quiet \ 1040 $CSA_TPL_ROOT/tw-newcontent-email.txt) { 1041 tpl_file = tw-newcontent-email.txt 1042 } else tpl_file = (--file-root $tw_dstem tw-newcontent-email.txt) 1043 1044 TNS_EMAIL_TO = $TNS_EMAIL_MODERATOR 1045 tpl.var.tw.url = \ 1046 $CSA_RPC_URI/$CSA_LANG/$'tbl_group.g_uri'/$'tbl_page.p_uri' 1047 csaSendMail $tpl_file 1048 } 1049 1050 # Take the client back to the updated page if we are in REST mode. 1051 1052 csaExit.ok $CSA_RPC_URI/$CSA_LANG/$'tbl_group.g_uri'/$'tbl_page.p_uri' 1053 1054 #EOF