├── LICENSE ├── Lua ├── arch.lua ├── arch2.lua ├── blogcat.lua ├── plotposts.lua ├── put.lua ├── table.lua └── url-util.lua ├── Makefile ├── NOTES ├── cvs.log ├── markup ├── notes └── notes.2 ├── README ├── TODO ├── htdocs ├── about │ └── index.html ├── errors │ ├── 403.html │ ├── 404.html │ └── 500.html └── htaccess.sample ├── journal ├── 2011 │ └── 11 │ │ └── 28 │ │ ├── 1 │ │ ├── 1.ad │ │ ├── 1.comments │ │ ├── 1.webmention │ │ ├── adtag │ │ ├── authors │ │ ├── class │ │ ├── status │ │ └── titles ├── .first ├── .last ├── apache.conf ├── atom │ ├── categories │ ├── entry │ └── main ├── blog.conf ├── html │ ├── ad │ ├── blog.body │ ├── comments │ ├── cond.hr │ ├── edit │ ├── entry │ ├── entry.cond.author │ ├── entry.cond.date │ ├── main │ ├── navigation.bar │ ├── navigation.bar.next │ ├── navigation.bar.prev │ ├── navigation.current │ ├── navigation.link │ ├── navigation.link.next │ ├── navigation.link.prev │ ├── webmention │ └── webmention.item ├── json │ ├── item │ └── main ├── posthook_script ├── posthook_template_script ├── prehook_script └── rss │ ├── item │ └── main └── src ├── addutil.c ├── authenticate.c ├── backend.c ├── backend.h ├── blog.c ├── blog.h ├── blogutil.c ├── blogutil.h ├── callbacks.c ├── conversion.c ├── conversion.h ├── entity-conversion.c ├── frontend.h ├── hooks.c ├── main.c ├── main.h ├── main_cgi.c ├── main_cli.c ├── misc.c ├── timeutil.c ├── timeutil.h ├── wbtum.c └── wbtum.h /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 19yy 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) 19yy name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /Lua/arch.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | -- ************************************************************************ 3 | -- Copyright 2018 by Sean Conner. All Rights Reserved. 4 | -- 5 | -- This program is free software; you can redistribute it and/or 6 | -- modify it under the terms of the GNU General Public License 7 | -- as published by the Free Software Foundation; either version 2 8 | -- of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | -- 19 | -- Comments, questions and criticisms can be sent to: sean@conman.org 20 | -- 21 | -- ************************************************************************ 22 | -- luacheck: ignore 611 23 | 24 | local lpeg = require "lpeg" 25 | local CONF = setmetatable({},{ __index = _G }) 26 | local g_start 27 | local g_end 28 | 29 | -- *********************************************************************** 30 | 31 | do 32 | local number = lpeg.R"09"^1 / tonumber 33 | local parse_date = lpeg.Ct( 34 | lpeg.Cg(number,"year") * lpeg.P"/" * 35 | lpeg.Cg(number,"month") * lpeg.P"/" * 36 | lpeg.Cg(number,"day") * lpeg.P"." * 37 | lpeg.Cg(number,"part") 38 | ) 39 | 40 | local function getdate(fname) 41 | local name = CONF.basedir .. "/" .. fname 42 | local f = io.open(name,"r") 43 | local d = f:read("*a") 44 | f:close() 45 | return parse_date:match(d) 46 | end 47 | 48 | local fname = arg[1] or os.getenv "BLOG_CONFIG" or "blog.conf" 49 | 50 | if _VERSION == "Lua 5.1" then 51 | local chunk = loadfile(fname) 52 | setfenv(chunk,CONF) 53 | chunk() 54 | else 55 | local chunk = loadfile(fname,"t",CONF) 56 | chunk() 57 | end 58 | 59 | setmetatable(CONF,nil) 60 | g_start = getdate(".first") 61 | g_end = getdate(".last") 62 | end 63 | 64 | -- *********************************************************************** 65 | 66 | local function anyposts(year,month) 67 | for day = 1 , 31 do 68 | local fname = string.format("%s/%04d/%02d/%02d/1",CONF.basedir,year,month,day) 69 | local f = io.open(fname,"r") 70 | if f then 71 | f:close() 72 | return true 73 | end 74 | end 75 | return false 76 | end 77 | 78 | -- *********************************************************************** 79 | 80 | local results = {} 81 | for year = g_start.year , g_end.year do 82 | results[year] = {} 83 | for month = 1 , 12 do 84 | results[year][month] = anyposts(year,month) 85 | end 86 | end 87 | 88 | for year = g_start.year , g_end.year , 6 do 89 | for y = year , year + 5 do 90 | if not results[y] then 91 | results[y] = 92 | { 93 | false , false , false , false , false , false , 94 | false , false , false , false , false , false , 95 | } 96 | end 97 | end 98 | end 99 | 100 | for year = g_start.year , g_end.year , 6 do 101 | local out = io.popen("table","w") 102 | local header = {} 103 | 104 | for y = year , year + 5 do 105 | table.insert(header,tostring(y)) 106 | end 107 | 108 | header[1] = "*" .. header[1] 109 | out:write(table.concat(header,"\t"),"\n") 110 | 111 | for month = 1 , 12 do 112 | local entry = {} 113 | for y = year , year + 5 do 114 | if results[y][month] then 115 | local date = os.time { year = y , month = month , day = 1 } 116 | local line = os.date([[%B]],date) 117 | table.insert(entry,line) 118 | else 119 | local date = os.time { year = y , month = month , day = 1 } 120 | local line = os.date([[%B]],date) 121 | table.insert(entry,line) 122 | end 123 | end 124 | 125 | out:write(table.concat(entry,"\t"),"\n") 126 | end 127 | 128 | out:close() 129 | print("

") 130 | end 131 | os.exit(0) 132 | -------------------------------------------------------------------------------- /Lua/arch2.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | -- ************************************************************************ 3 | -- Copyright 2018 by Sean Conner. All Rights Reserved. 4 | -- 5 | -- This program is free software; you can redistribute it and/or 6 | -- modify it under the terms of the GNU General Public License 7 | -- as published by the Free Software Foundation; either version 2 8 | -- of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | -- 19 | -- Comments, questions and criticisms can be sent to: sean@conman.org 20 | -- 21 | -- ************************************************************************ 22 | -- luacheck: ignore 611 23 | 24 | local lpeg = require "lpeg" 25 | local CONF = setmetatable({},{ __index = _G }) 26 | local g_start 27 | local g_end 28 | 29 | -- *********************************************************************** 30 | 31 | do 32 | local number = lpeg.R"09"^1 / tonumber 33 | local parse_date = lpeg.Ct( 34 | lpeg.Cg(number,"year") * lpeg.P"/" * 35 | lpeg.Cg(number,"month") * lpeg.P"/" * 36 | lpeg.Cg(number,"day") * lpeg.P"." * 37 | lpeg.Cg(number,"part") 38 | ) 39 | 40 | local function getdate(fname) 41 | local name = CONF.basedir .. "/" .. fname 42 | local f = io.open(name,"r") 43 | local d = f:read("*a") 44 | f:close() 45 | return parse_date:match(d) 46 | end 47 | 48 | local fname = arg[1] or os.getenv "BLOG_CONFIG" or "blog.conf" 49 | 50 | if _VERSION == "Lua 5.1" then 51 | local chunk = loadfile(fname) 52 | setfenv(chunk,CONF) 53 | chunk() 54 | else 55 | local chunk = loadfile(fname,"t",CONF) 56 | chunk() 57 | end 58 | 59 | setmetatable(CONF,nil) 60 | g_start = getdate(".first") 61 | g_end = getdate(".last") 62 | end 63 | 64 | -- *********************************************************************** 65 | 66 | local function anyposts(year,month) 67 | for day = 1 , 31 do 68 | local fname = string.format("%s/%04d/%02d/%02d/1",CONF.basedir,year,month,day) 69 | local f = io.open(fname,"r") 70 | if f then 71 | f:close() 72 | return true 73 | end 74 | end 75 | return false 76 | end 77 | 78 | -- *********************************************************************** 79 | 80 | io.stdout:write("\n ",year)) 84 | for month = 1 , 12 do 85 | local date = os.time { year = year , month = month , day = 1 } 86 | if anyposts(year,month) then 87 | io.stdout:write(os.date([[]],date)) 88 | else 89 | io.stdout:write(os.date([[]],date)) 90 | end 91 | end 92 | io.stdout:write("\n") 93 | end 94 | 95 | io.stdout:write(" \n
%d%b%b
\n") 96 | os.exit(0) 97 | -------------------------------------------------------------------------------- /Lua/blogcat.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | -- ************************************************************************ 3 | -- Copyright 2005 by Sean Conner. All Rights Reserved. 4 | -- 5 | -- This program is free software; you can redistribute it and/or 6 | -- modify it under the terms of the GNU General Public License 7 | -- as published by the Free Software Foundation; either version 2 8 | -- of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | -- 19 | -- Comments, questions and criticisms can be sent to: sean@conman.org 20 | -- 21 | -- ************************************************************************ 22 | -- luacheck: ignore 611 23 | 24 | local lpeg = require "lpeg" 25 | local CONF = setmetatable({},{ __index = _G }) 26 | local g_categories = {} 27 | local g_start 28 | local g_end 29 | 30 | -- *********************************************************************** 31 | 32 | do 33 | local number = lpeg.R"09"^1 / tonumber 34 | local parse_date = lpeg.Ct( 35 | lpeg.Cg(number,"year") * lpeg.P"/" * 36 | lpeg.Cg(number,"month") * lpeg.P"/" * 37 | lpeg.Cg(number,"day") * lpeg.P"." * 38 | lpeg.Cg(number,"part") 39 | ) 40 | 41 | local function getdate(fname) 42 | local name = CONF.basedir .. "/" .. fname 43 | local f = io.open(name,"r") 44 | local d = f:read("*a") 45 | f:close() 46 | return parse_date:match(d) 47 | end 48 | 49 | local fname = arg[1] or os.getenv "BLOG_CONFIG" or "blog.conf" 50 | 51 | if _VERSION == "Lua 5.1" then 52 | local chunk = loadfile(fname) 53 | setfenv(chunk,CONF) 54 | chunk() 55 | else 56 | local chunk = loadfile(fname,"t",CONF) 57 | chunk() 58 | end 59 | 60 | setmetatable(CONF,nil) 61 | g_start = getdate(".first") 62 | g_end = getdate(".last") 63 | end 64 | 65 | -- *********************************************************************** 66 | 67 | local parse_tags do 68 | local char = (lpeg.R" \255" - lpeg.P",") 69 | local text = lpeg.S" \t"^0 * lpeg.C(char^0) 70 | parse_tags = lpeg.Ct(text * (lpeg.P"," * text)^0) 71 | end 72 | 73 | local function collect_class(when) 74 | local tdate = string.format("%04d/%02d/%02d",when.year,when.month,when.day) 75 | local sclass = string.format("%s/%s/class", CONF.basedir,tdate) 76 | local stitles = string.format("%s/%s/titles",CONF.basedir,tdate) 77 | local fclass = io.open(sclass,"r") 78 | local ftitles = io.open(stitles,"r") 79 | local count = 1 80 | 81 | if fclass == nil then return end 82 | if not ftitles then 83 | print(stitles) 84 | os.exit(1) 85 | end 86 | 87 | for class in fclass:lines() do 88 | local title = ftitles:read("*l") 89 | if not title then title = "[no title]" end 90 | local tags = parse_tags:match(class) 91 | for _,tag in ipairs(tags) do 92 | if not g_categories[tag] then 93 | g_categories[tag] = {} 94 | end 95 | table.insert(g_categories[tag],string.format("%s.%d\t%s",tdate,count,title)) 96 | end 97 | count = count + 1 98 | end 99 | 100 | ftitles:close() 101 | fclass:close() 102 | end 103 | 104 | -- ********************************************************************* 105 | 106 | local function daysinmonth(year,month) 107 | local days = { 31 , 0 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 } 108 | 109 | if month == 2 then 110 | if year % 400 == 0 then return 29 end 111 | if year % 100 == 0 then return 28 end 112 | if year % 4 == 0 then return 29 end 113 | return 28 114 | end 115 | 116 | return days[month] 117 | end 118 | 119 | -- ********************************************************************* 120 | 121 | local function addday(today) 122 | today.day = today.day + 1 123 | if today.day > daysinmonth(today.year,today.month) then 124 | today.day = 1 125 | today.month = today.month + 1 126 | if today.month == 13 then 127 | today.month = 1 128 | today.year = today.year + 1 129 | end 130 | end 131 | today.hour = 1 132 | end 133 | 134 | -- ******************************************************************** 135 | 136 | local function timecmp(a,b) 137 | local res 138 | 139 | res = a.year - b.year 140 | if res ~= 0 then return res end 141 | res = a.month - b.month 142 | if res ~= 0 then return res end 143 | return a.day - b.day 144 | end 145 | 146 | -- ******************************************************************** 147 | 148 | while(timecmp(g_start,g_end) <= 0) do 149 | collect_class(g_start) 150 | addday(g_start) 151 | end 152 | 153 | local keys = {} 154 | for name in pairs(g_categories) do 155 | table.insert(keys,name) 156 | end 157 | 158 | table.sort(keys) 159 | 160 | for _,key in ipairs(keys) do 161 | io.stdout:write(string.format("%s\n",key)) 162 | for _,item in ipairs(g_categories[key]) do 163 | io.stdout:write(string.format("\t%s\n",item)) 164 | end 165 | end 166 | 167 | os.exit(0) 168 | -------------------------------------------------------------------------------- /Lua/plotposts.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | -- ************************************************************************ 3 | -- Copyright 2018 by Sean Conner. All Rights Reserved. 4 | -- 5 | -- This program is free software; you can redistribute it and/or 6 | -- modify it under the terms of the GNU General Public License 7 | -- as published by the Free Software Foundation; either version 2 8 | -- of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | -- 19 | -- Comments, questions and criticisms can be sent to: sean@conman.org 20 | -- 21 | -- ************************************************************************ 22 | -- luacheck: ignore 611 23 | 24 | local lpeg = require "lpeg" 25 | local CONF = setmetatable({},{ __index = _G }) 26 | local g_start 27 | local g_end 28 | 29 | -- *********************************************************************** 30 | 31 | do 32 | local number = lpeg.R"09"^1 / tonumber 33 | local parse_date = lpeg.Ct( 34 | lpeg.Cg(number,"year") * lpeg.P"/" * 35 | lpeg.Cg(number,"month") * lpeg.P"/" * 36 | lpeg.Cg(number,"day") * lpeg.P"." * 37 | lpeg.Cg(number,"part") 38 | ) 39 | 40 | local function getdate(fname) 41 | local name = CONF.basedir .. "/" .. fname 42 | local f = io.open(name,"r") 43 | local d = f:read("*a") 44 | f:close() 45 | return parse_date:match(d) 46 | end 47 | 48 | local fname = arg[1] or os.getenv "BLOG_CONFIG" or "blog.conf" 49 | 50 | if _VERSION == "Lua 5.1" then 51 | local chunk = loadfile(fname) 52 | setfenv(chunk,CONF) 53 | chunk() 54 | else 55 | local chunk = loadfile(fname,"t",CONF) 56 | chunk() 57 | end 58 | 59 | setmetatable(CONF,nil) 60 | g_start = getdate(".first") 61 | g_end = getdate(".last") 62 | end 63 | 64 | -- *********************************************************************** 65 | 66 | local function numposts(year,month) 67 | local sum = 0 68 | 69 | for day = 1 , 31 do 70 | local fname = string.format("%s/%04d/%02d/%02d/titles",CONF.basedir,year,month,day) 71 | local f = io.open(fname,"r") 72 | if f then 73 | for _ in f:lines() do 74 | sum = sum + 1 75 | end 76 | f:close() 77 | end 78 | end 79 | 80 | return sum 81 | end 82 | 83 | -- *********************************************************************** 84 | 85 | for year = g_start.year , g_end.year do 86 | local first 87 | local last 88 | 89 | if year == g_start.year then 90 | first = g_start.month 91 | else 92 | first = 1 93 | end 94 | 95 | if year == g_end.year then 96 | last = g_end.month 97 | else 98 | last = 12 99 | end 100 | 101 | for month = first , last do 102 | local sum = numposts(year,month) 103 | local hdr = os.date("%b %Y",os.time { year = year , month = month , day = 1 }) 104 | print(hdr,sum) 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /Lua/put.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | -- luacheck: ignore 611 3 | 4 | local dump = require "org.conman.table".dump 5 | local tls = require "org.conman.net.tls" 6 | local tcp = require "org.conman.net.tcp" 7 | local url = require "org.conman.parsers.url" 8 | local magic = require "org.conman.fsys.magic" 9 | local fsys = require "org.conman.fsys" 10 | local http = require "org.conman.parsers.http-client" 11 | local getopt = require "org.conman.getopt".getopt 12 | local ih = require "org.conman.parsers.ih" 13 | local base64 = require "org.conman.base64"() 14 | local uurl = require "url-util" 15 | local status = require "org.conman.const.http-response" 16 | local zlib = require "zlib" 17 | 18 | local headers = ih.headers(ih.generic) 19 | local gf_debug 20 | 21 | magic:flags('mime') 22 | 23 | -- ************************************************************************ 24 | 25 | local function errmsg(...) 26 | io.stderr:write(string.format(...),'\n') 27 | return 1 28 | end 29 | 30 | -- ************************************************************************ 31 | 32 | local function msg(...) 33 | io.stdout:write(string.format(...),'\n') 34 | end 35 | 36 | -- ************************************************************************ 37 | 38 | local function readbody(conn,hdrs) 39 | local body 40 | 41 | if hdrs['Content-Length'] then 42 | body = conn:read(hdrs['Content-Length']) 43 | elseif hdrs['Transfer-Encoding'] == 'chunked' then 44 | body = "" 45 | repeat 46 | local line = conn:read("l") 47 | if not line then break end 48 | local len = tonumber(line,16) 49 | if not len then break end 50 | body = body .. (conn:read(len) or "") 51 | conn:read("l") 52 | until len == 0 53 | else 54 | body = "" 55 | end 56 | 57 | if hdrs['Content-Encoding'] == 'gzip' then 58 | local s = zlib.inflate(body) 59 | body = s:read("*a") 60 | end 61 | 62 | return body 63 | end 64 | 65 | -- *********************************************************************** 66 | 67 | local function get(conn,location) 68 | local path do 69 | if location.query then 70 | path = string.format("%s?%s",location.path,location.query) 71 | else 72 | path = location.path 73 | end 74 | end 75 | 76 | conn:write( 77 | string.format("GET %s HTTP/1.1\r\n",path), 78 | string.format("Host: %s:%d\r\n",location.host,location.port), 79 | "User-Agent: mod-blog-updater/1.0\r\n", 80 | "Accept: text/plain\r\n", 81 | "\r\n" 82 | ); 83 | conn:flush() 84 | 85 | local response = conn:read("l") 86 | local hdrs = conn:read("h") 87 | 88 | response = http.response:match(response) 89 | hdrs = http.headers:match(hdrs) 90 | local body = readbody(conn,hdrs) 91 | 92 | if gf_debug then 93 | dump("GET response",response) 94 | dump("GET headers",hdrs) 95 | dump("GET body",body) 96 | end 97 | 98 | if response.status == status.SUCCESS.OKAY then 99 | return body,response.status 100 | else 101 | return nil,response.status 102 | end 103 | end 104 | 105 | -- *********************************************************************** 106 | 107 | local function put(conn,location,hdrs,mimetype,body) 108 | conn:write( 109 | string.format("PUT %s HTTP/1.1\r\n",location.path), 110 | string.format("Host: %s:%d\r\n",location.host,location.port), 111 | "User-Agent: mod-blog-updater/1.0\r\n", 112 | string.format("Content-Type: %s\r\n",mimetype), 113 | string.format("Content-Length: %d\r\n",#body), 114 | "Accept: */*\r\n" 115 | ) 116 | 117 | for name,val in pairs(hdrs) do 118 | conn:write(string.format("%s: %s\r\n",name,val)) 119 | end 120 | 121 | conn:write("\r\n",body) 122 | conn:flush() 123 | 124 | local r = conn:read("l") 125 | local h = conn:read("h") 126 | 127 | local res = http.response:match(r) 128 | local hdr = http.headers:match(h) 129 | body = readbody(conn,hdr) 130 | 131 | if gf_debug then 132 | dump("PUT response",res) 133 | dump("PUT headers",hdr) 134 | dump("PUT body",body) 135 | end 136 | 137 | return res.status >= 200 and res.status <= 299 , res.status 138 | end 139 | 140 | -- ************************************************************************ 141 | 142 | local function loadentry(filename) 143 | local f,err = io.open(filename,"r") 144 | if not f then 145 | errmsg("%s: %s",filename,err) 146 | return 147 | end 148 | 149 | local d = f:read("*a") 150 | f:close() 151 | local hdrs,pos = headers:match(d) -- XXX lower case for now 152 | 153 | if not hdrs then 154 | errmsg("%s: not a proper entry") 155 | return 156 | end 157 | 158 | if not hdrs.title then 159 | errmsg("%s: missing TITLE",filename) 160 | return 161 | end 162 | 163 | if not hdrs.class then 164 | errmsg("%s: missing CLASS",filename) 165 | return 166 | end 167 | 168 | if not hdrs.date then 169 | hdrs.date = os.date("%Y/%m/%d") 170 | end 171 | 172 | local body = d:sub(pos,-1) 173 | 174 | if (#body == 0) then 175 | errmsg("%s: missing text",filename) 176 | return 177 | end 178 | 179 | return hdrs,body 180 | end 181 | 182 | -- ************************************************************************ 183 | 184 | local function usage() 185 | io.stderr:write(string.format([[ 186 | usage: %s [options] entryfile [files...] 187 | -b | --blog blogref 188 | -d | --debug 189 | -h | --help 190 | ]],arg[0])) 191 | os.exit(1,true) 192 | end 193 | 194 | -- ************************************************************************ 195 | 196 | local CONF = "/home/spc/.config/mod-blog-put.config" 197 | local BLOG = 'default' 198 | local BASEURL 199 | local USER 200 | 201 | local opts = 202 | { 203 | { "b" , "blog" , true , function(b) BLOG = b end }, 204 | { "d" , "debug" , false , function() gf_debug = true end }, 205 | { "h" , "help" , false , function() usage() end }, 206 | } 207 | 208 | local fi = getopt(arg,opts) 209 | 210 | if not arg[fi] then 211 | os.exit(errmsg("missing entry file"),true) 212 | end 213 | 214 | do 215 | local confenv = {} 216 | local conf,err = loadfile(CONF,"t",confenv) 217 | if not conf then 218 | os.exit(errmsg("%s: %s",CONF,err),true) 219 | end 220 | 221 | conf() 222 | 223 | if not confenv[BLOG] then 224 | os.exit(errmsg("%s: missing %q config block",CONF,BLOG)) 225 | end 226 | 227 | BASEURL = confenv[BLOG].url 228 | 229 | if not BASEURL then 230 | os.exit(errmsg("%s: missing %s.url directive",CONF,BLOG)) 231 | end 232 | 233 | USER = confenv[BLOG].user 234 | if USER then 235 | USER = "Basic " .. base64:encode(USER) 236 | end 237 | end 238 | 239 | if gf_debug then 240 | io.stderr:write(string.format([[ 241 | base-url=%q 242 | entry-file=%q 243 | ]],BASEURL,arg[fi])) 244 | end 245 | 246 | local hdrs,body = loadentry(arg[fi]) 247 | if not hdrs then 248 | os.exit(1,true) 249 | end 250 | 251 | local base = url:match(BASEURL) 252 | local path = url:match(hdrs.date .. '/' .. fsys.basename(arg[fi])) 253 | local location = uurl.merge(base,path) 254 | 255 | if gf_debug then 256 | io.stderr:write(string.format("location: %s body: %d\n",uurl.toa(location),#body)) 257 | end 258 | 259 | local conn,err do 260 | if location.scheme == 'https' then 261 | conn,err = tls.connect(location.host,location.port) 262 | else 263 | conn,err = tcp.connect(location.host,location.port) 264 | end 265 | end 266 | 267 | if not conn then 268 | os.exit(errmsg("%s: %s",location.host,err)) 269 | end 270 | 271 | local newurl do 272 | local basen = url:match(BASEURL) 273 | local pathn = url:match("/boston.cgi?cmd=last&date="..hdrs.date) 274 | local locationn = uurl.merge(basen,pathn) 275 | newurl = get(conn,locationn) 276 | 277 | local front,part = newurl:match("^(.*)(%d+)$") 278 | part = tonumber(part) + 1 279 | newurl = front .. part 280 | end 281 | 282 | if gf_debug then 283 | io.stderr:write(string.format("newurl=%q\n",newurl)) 284 | end 285 | 286 | location = url:match(newurl) 287 | 288 | local http_headers = {} do 289 | for name,val in pairs(hdrs) do 290 | http_headers['Blog-'..name] = val 291 | end 292 | 293 | if USER then 294 | http_headers['Authorization'] = USER 295 | end 296 | 297 | if fi == #arg then 298 | http_headers['Connection'] = 'close' 299 | end 300 | 301 | if gf_debug then 302 | dump("headers",http_headers) 303 | end 304 | end 305 | 306 | msg("PUT %s (%d)",uurl.toa(location),#body) 307 | if not put(conn,location,http_headers,"text/html",body) then 308 | conn:close() 309 | errmsg("Failed: PUT %s",uurl.toa(location)) 310 | errmsg("\tForgot credentials?") 311 | os.exit(1,true) 312 | end 313 | 314 | for i = fi + 1 , #arg do 315 | local f,err2 = io.open(arg[i],"rb") 316 | if f then 317 | local bodyf = f:read("*a") 318 | f:close() 319 | 320 | local pathf = url:match(hdrs.date .. '/' .. fsys.basename(arg[i])) 321 | local locationf = uurl.merge(base,pathf) 322 | 323 | if gf_debug then 324 | io.stderr:write(string.format( 325 | "location: %s body: %d mime: %s\n", 326 | uurl.toa(locationf), 327 | #bodyf, 328 | magic(bodyf,true) 329 | )) 330 | end 331 | 332 | local http_headersf = { ['Blog-File'] = true } 333 | 334 | if USER then 335 | http_headersf['Authorization'] = USER 336 | end 337 | 338 | if i == #arg then 339 | http_headersf['Connection'] = 'close' 340 | end 341 | 342 | if gf_debug then 343 | dump("headers",http_headersf) 344 | end 345 | 346 | msg("PUT %s (%d)",uurl.toa(locationf),#bodyf) 347 | put(conn,locationf,http_headersf,magic(bodyf,true),bodyf) 348 | else 349 | errmsg("%s: %s",arg[i],err2) 350 | end 351 | end 352 | 353 | conn:close() 354 | -------------------------------------------------------------------------------- /Lua/table.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | -- ************************************************************************ 3 | -- Copyright 2005 by Sean Conner. All Rights Reserved. 4 | -- 5 | -- This program is free software; you can redistribute it and/or 6 | -- modify it under the terms of the GNU General Public License 7 | -- as published by the Free Software Foundation; either version 2 8 | -- of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | -- 19 | -- Comments, questions and criticisms can be sent to: sean@conman.org 20 | -- 21 | -- ************************************************************************ 22 | -- luacheck: ignore 611 23 | -- 24 | -- ======================================================================== 25 | -- Generate a table from text. 26 | -- 27 | -- Format: 28 | -- 29 | -- #this is a caption 30 | -- *headerline field2 field3 field4 31 | -- **footerline field2 field3 field4 32 | -- dataitem data2 data3 data4 33 | -- 34 | -- caption, header and footer marker are optional. 35 | -- Fields are tab-delimeted. 36 | -- ******************************************************************* 37 | -- luacheck: ignore 611 38 | 39 | local lpeg = require "lpeg" 40 | 41 | local Cc = lpeg.Cc 42 | local Cs = lpeg.Cs 43 | local P = lpeg.P 44 | local R = lpeg.R 45 | local S = lpeg.S 46 | 47 | local nl = P"\n" 48 | local tab = P"\t" 49 | local text = R(" ~","\128\255")^1 50 | local int = P"0" + (R"19" * R"09"^0) 51 | local frac = P"." * R"09"^1 52 | local exp = S"Ee" * S"+-"^-1 * R"09"^1 53 | local number = (P"-"^-1 * int * frac^-1 * exp^-1) 54 | 55 | local td = Cc'' * number * Cc'' * tab^-1 56 | + Cc'' * text * Cc'' * tab^-1 57 | local th = Cc'' * text * Cc'' * tab^-1 58 | local tr = Cc' ' * td^1 * Cc'' * nl 59 | 60 | local caption = P"#" / "" 61 | * Cc' ' * text * Cc'' * nl 62 | local header = P"*" / "" 63 | * Cc' \n ' * th^1 * Cc'\n ' * nl 64 | local footer = P"**" / "" 65 | * Cc' \n ' * th^1 * Cc'\n ' * nl 66 | local htable = Cs( 67 | Cc'\n' 68 | * caption^-1 69 | * header^-1 70 | * footer^-1 71 | * Cc' \n' * tr^1 * Cc' \n' 72 | * Cc'
\n' 73 | ) 74 | 75 | print(htable:match(io.stdin:read("*a"))) 76 | -------------------------------------------------------------------------------- /Lua/url-util.lua: -------------------------------------------------------------------------------- 1 | -- *************************************************************** 2 | -- 3 | -- Copyright 2019 by Sean Conner. All Rights Reserved. 4 | -- 5 | -- This library is free software; you can redistribute it and/or modify it 6 | -- under the terms of the GNU Lesser General Public License as published by 7 | -- the Free Software Foundation; either version 3 of the License, or (at your 8 | -- option) any later version. 9 | -- 10 | -- This library is distributed in the hope that it will be useful, but 11 | -- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 | -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 13 | -- License for more details. 14 | -- 15 | -- You should have received a copy of the GNU Lesser General Public License 16 | -- along with this library; if not, see . 17 | -- 18 | -- Comments, questions and criticisms can be sent to: sean@conman.org 19 | -- 20 | -- ******************************************************************** 21 | -- luacheck: globals rm_dot_segs merge toa query toq 22 | -- luacheck: globals esc_auth esc_path esc_query esc_frag 23 | -- luacheck: ignore 611 24 | 25 | local gtypes = require "org.conman.const.gopher-types" 26 | local lpeg = require "lpeg" 27 | local string = require "string" 28 | local table = require "table" 29 | 30 | local tonumber = tonumber 31 | local tostring = tostring 32 | local type = type 33 | local pairs = pairs 34 | 35 | _ENV = {} 36 | 37 | local Cc = lpeg.Cc 38 | local Cf = lpeg.Cf 39 | local Cg = lpeg.Cg 40 | local Cs = lpeg.Cs 41 | local C = lpeg.C 42 | local P = lpeg.P 43 | local R = lpeg.R 44 | 45 | -- ******************************************************************** 46 | -- Note: The commented letters A, B, C, etc., reference RFC-3986 5.2.4. 47 | -- ******************************************************************** 48 | 49 | local segment = P'../' * Cc'' * Cc(false) -- A 50 | + P'./' * Cc'' * Cc(false) 51 | + P'/.' * #P'/' * Cc'' * Cc(false) -- B 52 | + P'/.' * P(-1) * Cc'/' * Cc(false) 53 | + P'/..' * #P'/' * Cc'' * Cc(true) -- C 54 | + P'/..' * P(-1) * Cc'/' * Cc(true) 55 | + P'..' * P(-1) * Cc'' * Cc(false) -- D 56 | + P'.' * P(-1) * Cc'' * Cc(false) 57 | + C(P'/'^-1 * (P(1) - P'/')^1) * Cc(false) -- E 58 | + C'/' * Cc(false) 59 | 60 | rm_dot_segs = Cf(Cc"" * (Cg(segment))^1,function(acc,capture,trim) 61 | if trim then 62 | for i = -2,-#acc,-1 do 63 | if acc:sub(i,i) == '/' then 64 | return acc:sub(1,i-1) .. capture 65 | end 66 | end 67 | return capture 68 | else 69 | return acc .. capture 70 | end 71 | end) + Cc'' 72 | 73 | -- ******************************************************************** 74 | -- Usage: u = merge(Base,R) 75 | -- Desc: Merge a URL with a base URL 76 | -- Input: Base (table) a URL from url2:match() 77 | -- R (table) a URL from url2:match() 78 | -- Return: u (table) A merged URL 79 | -- 80 | -- Note: Implements the algorithm from RFC-3986 5.2.2, with the 81 | -- "authority" portion being .host, .port and .user. 82 | -- ******************************************************************** 83 | 84 | function merge(Base,Ru) 85 | local function mergepath() 86 | if Base.host and Base.path == "" then 87 | return "/" .. Ru.path 88 | else 89 | for i = -1 , -#Base.path , -1 do 90 | if Base.path:sub(i,i) == '/' then 91 | return Base.path:sub(1,i) .. Ru.path 92 | end 93 | end 94 | end 95 | end 96 | 97 | local T = {} 98 | 99 | if Ru.scheme then 100 | T.scheme = Ru.scheme 101 | T.host = Ru.host -- authority 102 | T.port = Ru.port 103 | T.user = Ru.user 104 | T.path = Ru.path 105 | T.path = rm_dot_segs:match(T.path) 106 | T.query = Ru.query 107 | else 108 | if Ru.host then 109 | T.host = Ru.host 110 | T.port = Ru.port 111 | T.user = Ru.user 112 | T.path = rm_dot_segs:match(Ru.path) 113 | T.query = Ru.query 114 | else 115 | if Ru.path == "" then 116 | T.path = Base.path 117 | if Ru.query then 118 | T.query = Ru.query 119 | else 120 | T.query = Base.query 121 | end 122 | else 123 | if Ru.path:match "^/" then 124 | T.path = rm_dot_segs:match(Ru.path) 125 | else 126 | T.path = mergepath() 127 | T.path = rm_dot_segs:match(T.path) 128 | end 129 | T.query = Ru.query 130 | end 131 | T.host = Base.host 132 | T.port = Base.port 133 | T.user = Base.user 134 | end 135 | T.scheme = Base.scheme 136 | end 137 | T.fragment = Ru.fragment 138 | return T 139 | end 140 | 141 | -- ******************************************************************** 142 | 143 | local function tohex(c) 144 | return string.format("%%%02X",string.byte(c)) 145 | end 146 | 147 | local unsafe = P" " / "%%20" 148 | + P"#" / "%%23" 149 | + P"%" / "%%25" 150 | + P"<" / "%%3C" 151 | + P">" / "%%3E" 152 | + P"[" / "%%5B" 153 | + P"\\" / "%%5C" 154 | + P"]" / "%%5D" 155 | + P"^" / "%%5E" 156 | + P"{" / "%%7B" 157 | + P"|" / "%%7C" 158 | + P"}" / "%%7D" 159 | + P'"' / "%%22" 160 | + R("\0\31","\127\255") / tohex 161 | local char_auth = P"?" / "%%3F" 162 | + P"@" / "%%40" 163 | + unsafe 164 | + P(1) 165 | local char_path = P"?" / "%%3F" 166 | + unsafe 167 | + P(1) 168 | local char_query = P"=" / "%%3D" 169 | + P"&" / "%%26" 170 | + unsafe 171 | + P(1) 172 | local char_frag = unsafe 173 | + P(1) 174 | esc_auth = Cs(char_auth^0) 175 | esc_path = Cs(char_path^0) 176 | esc_query = Cs(char_query^0) 177 | esc_frag = Cs(char_frag^0) 178 | 179 | -- ******************************************************************** 180 | 181 | local xdigit = lpeg.locale().xdigit 182 | local char = lpeg.P"%" * lpeg.C(xdigit * xdigit) 183 | / function(c) 184 | return string.char(tonumber(c,16)) 185 | end 186 | + lpeg.R"!~" 187 | local name = lpeg.Cs((char - (lpeg.P"=" + lpeg.P"&"))^1) 188 | local value = lpeg.P"=" * lpeg.Cs((char - lpeg.P"&")^1) 189 | + lpeg.Cc(true) 190 | 191 | query = lpeg.Cf( 192 | lpeg.Ct"" * lpeg.Cg(name * value * lpeg.P"&"^-1)^0, 193 | function(result,n,v) 194 | if result and not result[n] then 195 | result[n] = v 196 | return result 197 | else 198 | return false 199 | end 200 | end 201 | ) 202 | 203 | -- ******************************************************************** 204 | 205 | function toq(q) 206 | local res = {} 207 | for n,v in pairs(q) do 208 | if v == true then 209 | table.insert(res,esc_query:match(n)) 210 | else 211 | table.insert(res,string.format("%s=%s", 212 | esc_query:match(tostring(n)), 213 | esc_query:match(tostring(v)) 214 | )) 215 | end 216 | end 217 | return table.concat(res,"&") 218 | end 219 | 220 | -- ******************************************************************** 221 | -- Note: From RFC-3986 5.3 222 | -- ******************************************************************** 223 | 224 | local SCHEMES = 225 | { 226 | http = 80, 227 | https = 443, 228 | ftp = 21, 229 | gemini = 1965, 230 | } 231 | 232 | function toa(u) 233 | local authority 234 | 235 | if u.host then 236 | authority = "" 237 | if u.user then authority = esc_auth:match(u.user) .. "@" end 238 | if u.host:match"%:" then 239 | authority = authority .. "[" .. esc_auth:match(u.host) .. "]" 240 | else 241 | authority = authority .. esc_auth:match(u.host) 242 | end 243 | if u.port and u.port ~= SCHEMES[u.scheme] then 244 | authority = authority .. ':' .. tostring(u.port) 245 | end 246 | end 247 | 248 | local result = "" 249 | if u.scheme then result = result .. u.scheme .. ':' end 250 | if authority then result = result .. "//" .. authority end 251 | 252 | if u.scheme == 'gopher' then 253 | result = result .. "/" .. gtypes[u.type] .. esc_path:match(u.selector) 254 | if u.search then 255 | result = result .. "%09" .. toq(u.search) 256 | end 257 | else 258 | result = result .. esc_path:match(u.path) 259 | 260 | if u.query then 261 | if type(u.query) == 'table' then 262 | result = result .. "?" .. toq(u.query) 263 | else 264 | result = result .. "?" .. u.query 265 | end 266 | end 267 | 268 | if u.fragment then result = result .. "#" .. esc_frag:match(u.fragment) end 269 | end 270 | return result 271 | end 272 | 273 | -- ******************************************************************** 274 | 275 | return _ENV 276 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ######################################################################### 2 | # 3 | # Copyright 2000 by Sean Conner. All Rights Reserved. 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | # 19 | # Comments, questions and criticisms can be sent to: sean@conman.org 20 | # 21 | ######################################################################## 22 | 23 | VERSION := $(shell git describe --tag) 24 | 25 | ifeq ($(VERSION),) 26 | VERSION=v64 27 | endif 28 | 29 | CC = gcc -std=c99 -Wall -Wextra -pedantic -Wwrite-strings 30 | CFLAGS = -g 31 | LDFLAGS = -g 32 | LDLIBS = -lgdbm -lcgi8 -llua -lm -ldl 33 | SETUID = /bin/chmod 34 | 35 | INSTALL = /usr/bin/install 36 | INSTALL_PROGRAM = $(INSTALL) 37 | INSTALL_NAME = boston 38 | 39 | override CFLAGS +=-D_GNU_SOURCE -DPROG_VERSION='"$(VERSION)"' 40 | override LDFLAGS +=-rdynamic 41 | 42 | prefix = /usr/local 43 | bindir = $(prefix)/bin 44 | 45 | ####################################################################### 46 | 47 | .PHONY: clean dist depend install uninstall reinstall 48 | 49 | src/main : $(patsubst %.c,%.o,$(wildcard src/*.c)) 50 | 51 | install: 52 | $(INSTALL) -d $(DESTDIR)$(bindir) 53 | $(INSTALL_PROGRAM) src/main $(DESTDIR)$(bindir)/$(INSTALL_NAME) 54 | $(SETUID) 4755 $(DESTDIR)$(bindir)/$(INSTALL_NAME) 55 | 56 | uninstall: 57 | $(RM) $(DESTDIR)$(bindir)/$(INSTALL_NAME) 58 | 59 | reinstall: 60 | make uninstall 61 | make install 62 | 63 | clean : 64 | $(RM) $(shell find . -name '*~') 65 | $(RM) $(shell find . -name '*.o') 66 | $(RM) src/main Makefile.bak 67 | 68 | dist: 69 | git archive -o /tmp/mod_blog-$(VERSION).tar.gz --prefix mod_blog/ $(VERSION) 70 | 71 | depend: 72 | makedepend -Y -- $(CFLAGS) -- src/*.c 2>/dev/null 73 | 74 | # DO NOT DELETE 75 | 76 | src/addutil.o: src/conversion.h src/frontend.h src/wbtum.h src/timeutil.h 77 | src/addutil.o: src/blog.h src/backend.h src/blogutil.h 78 | src/authenticate.o: src/frontend.h src/wbtum.h src/timeutil.h src/blog.h 79 | src/backend.o: src/blogutil.h src/backend.h src/frontend.h src/wbtum.h 80 | src/backend.o: src/timeutil.h src/blog.h 81 | src/blog.o: src/blog.h src/timeutil.h src/wbtum.h 82 | src/blogutil.o: src/blogutil.h 83 | src/callbacks.o: src/backend.h src/frontend.h src/wbtum.h src/timeutil.h 84 | src/callbacks.o: src/blog.h src/blogutil.h src/conversion.h 85 | src/conversion.o: src/conversion.h src/frontend.h src/wbtum.h src/timeutil.h 86 | src/conversion.o: src/blog.h src/blogutil.h 87 | src/main.o: src/main.h src/frontend.h src/wbtum.h src/timeutil.h src/blog.h 88 | src/main_cgi.o: src/backend.h src/frontend.h src/wbtum.h src/timeutil.h 89 | src/main_cgi.o: src/blog.h src/conversion.h src/main.h 90 | src/main_cli.o: src/backend.h src/frontend.h src/wbtum.h src/timeutil.h 91 | src/main_cli.o: src/blog.h src/blogutil.h src/conversion.h src/main.h 92 | src/misc.o: src/frontend.h src/wbtum.h src/timeutil.h src/blog.h 93 | src/misc.o: src/conversion.h 94 | src/timeutil.o: src/wbtum.h src/timeutil.h 95 | src/wbtum.o: src/wbtum.h src/timeutil.h 96 | -------------------------------------------------------------------------------- /NOTES/markup: -------------------------------------------------------------------------------- 1 | (xref http://cairnarvon.rotahall.org/2010/05/25/towards-a-better-bbcode/) 2 | (xref http://cairnarvon.rotahall.org/misc/sexpcode.html) 3 | 4 | {b This} is {i {u export} {o mark-up}} 5 | 6 | This is export mark-up 7 | 8 | {b.i.o.u EXPERT} 9 | 10 | EXPERT 11 | 12 | {sup*2 This text is superscripted two levels} 13 | 14 | This text is superscripted two levels 15 | 16 | n{sup 2} 17 | 18 | n^2 19 | 20 | {b.sup*2.i Way to be a dick, {sub*2.u.o dick}.} 21 | 22 | Way to be a dick, 23 | dick 24 | 25 | -------------------------------------------------------------------------------- /NOTES/notes: -------------------------------------------------------------------------------- 1 | Thinking of redoing this (sigh) 2 | 3 | https://developers.facebook.com/docs/pages/getting-started 4 | 5 | The curl command: 6 | -i include headers 7 | -X POST use method 8 | 9 | or maybe 10 | 11 | https://developers.facebook.com/docs/sharing/reference/send-dialog 12 | 13 | But then: 14 | 15 | The publish_actions permission will be deprecated. This permission 16 | granted apps access to publish posts to Facebook as the logged in 17 | user. Apps created from today onwards will not have access to this 18 | permission. Apps created before today that have been previously 19 | approved to request publish_actions can continue to do so until 20 | August 1, 2018. No further apps will be approved to use 21 | publish_actions via app review. Developers currently utilizing 22 | publish_actions are encouraged to switch to Facebook's Share dialogs 23 | for web, iOS and Android. 24 | 25 | https://developers.facebook.com/blog/post/2018/04/24/new-facebook-platform-product-changes-policy-updates/ 26 | 27 | --- 28 | 29 | 30 | 31 | 32 | NOTE: A lot of this is outdated information, but kept here for hysterical 33 | rasins. 34 | 35 | 36 | https://community.letsencrypt.org/t/end-of-life-plan-for-acmev1/88430 37 | 38 | --- 39 | 40 | 2019-04-28T16:54:00-05:00 41 | 42 | https://dev.to/kenbellows/stop-using-so-many-divs-an-intro-to-semantic-html-3i9i 43 | 44 | Using HTML5. 45 | 46 | --- 47 | 48 | 2019-03-30T20:46:00-05:00 49 | 50 | Yup. To add to that, posting a reply to your own blog and setting 51 | rel=“in-reply-to” and notifying the author with Webmention provides standard 52 | decentralized comments/notifications. See aaronparecki.com and the indieweb 53 | community for examples. 54 | 55 | also 56 | 57 | https://news.ycombinator.com/item?id=19532614 58 | 59 | 60 | 2018-04-03T21:43-05:00 61 | Webmention: 62 | https://webmention.rocks/ 63 | https://indieweb.org/Webmention-developer 64 | https://www.w3.org/TR/webmention/ 65 | 66 | Meta information for HTML5 67 | check source of view-source:https://tommorris.org/pages/blogroll 68 | 69 | https://indieweb.org/lost_infrastructure 70 | 71 | 2007-08-29T16:44-05:00 72 | 73 | About adding backlinks in old entries to new entries, one thing to 74 | keep in mind is to put said links at the top, according to 75 | 76 | http://www.wordyard.com/2007/08/28/can-newspapers-fix-old-errors/#comment-978 77 | 78 | 2016-12-23T03:28-05:00 79 | 80 | After spending a few hours on this, I realized that it would be 81 | harder than expected to support. I would either have to seriously 82 | restructure the code to support this feature, or parse the entry 83 | twice, which is doable, but doesn't sit well with me. 84 | 85 | So, I'm putting this here in the notes, where it will languish for 86 | years, instead of in the TODO, where it did languish for years. 87 | 88 | --- 89 | 90 | http://www.eglug.org/book/export/html/1923 91 | 92 | http://10up.github.io/Engineering-Best-Practices/css/#syntax-formatting 93 | 94 | https://www.google.com/webmasters/tools/mobile-friendly/ 95 | http://googlewebmastercentral.blogspot.com/2014/11/helping-users-find-mobile-friendly-pages.html 96 | http://googlewebmastercentral.blogspot.com/2015/02/finding-more-mobile-friendly-search.html 97 | https://developers.google.com/web/fundamentals/layouts/?hl=en 98 | 99 | https://css-tricks.com/snippets/css/media-queries-for-standard-devices/ 100 | http://stackoverflow.com/questions/3839809/detect-iphone-ipad-purely-by-css 101 | http://stackoverflow.com/questions/14914407/css-detect-either-iphone-ipad-or-any-other-handheld 102 | https://news.ycombinator.com/item?id=9413992 103 | 104 | 105 | http://ogp.me/ 106 | http://openlike.org/ 107 | (uses http://www.w3.org/TR/rdfa-core/) 108 | 109 | 110 | Maybe integrate discount (see ~/apps) as a filter. 111 | http://www.pell.portland.or.us/~orc/Code/markdown/ 112 | 113 | How about this: 114 | 115 | #define --- — 116 | #define -- – 117 | #define `` ‘ 118 | #define '' ” 119 | #define ... … 120 | 121 | or some similar type of declaration for inputing data? 122 | 123 | see 2007/01/09.1 for some details 124 | 125 | ---------- 126 | 127 | 128 | 129 | restructuring both bp.c and addentry.c 130 | 131 | 132 | 133 | int main(int argc,char *argv[]) 134 | { 135 | Cgi cgi; 136 | 137 | MemInit(); 138 | BufferInit(); 139 | DdtInit(); 140 | CleanInit(); 141 | 142 | if (CgiNew(&cgi,NULL) == ERR_OKAY) 143 | { 144 | switch(CgiMethod(cgi)) 145 | { 146 | case GET: rc = main_cgi_get (cgi,argv,argc); break; 147 | case POST: rc = main_cgi_post(cgi,argv,argc); break; 148 | default: rc = XXX ; break; 149 | } 150 | } 151 | else 152 | rc = main_cli(argc,argv); 153 | 154 | return(rc); 155 | } 156 | 157 | int main_cgi_get(Cgi cgi,int argc,char *argv[]) 158 | { 159 | } 160 | 161 | int main_cgi_post(Cgi cgi,int argc,char *argv[]) 162 | { 163 | } 164 | 165 | int main_cli(int argc,char *argv[]) 166 | { 167 | } 168 | 169 | 170 | 171 | GET: 172 | $PATH_INFO tumbler 173 | 174 | POST: 175 | email = 'yes' | 'no' 176 | filter = 'text' | 'mixed' | 'html' 177 | update = 'new' | 'modify' | 'edit' | 'template' | 'other' 178 | author = string || $REMOTE_USER 179 | title = string 180 | class = string 181 | date = string 182 | body = string 183 | cmd = --> 'new' | 'show' | 'edit' | 'delete' 184 | 185 | 186 | PUT: 187 | 188 | DELETE: 189 | 190 | 191 | CLI: 192 | --config filespec 193 | --cmd 'new' | --> 'show' [ | 'edit' | 'delete' ] 194 | --file input 195 | --stdin read from stdin 196 | --email read from stdin as email 197 | --update --> 'new' | 'modify' | 'edit' | 'template' | 'other' 198 | --entry tumbler 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | ------------------------------------------------------------------- 213 | 214 | Permissions for transcluding----look into adding a query string with 215 | permissions. 216 | 217 | 218 | Adding comments. 219 | 220 | Adding a new field to struct blogentry 221 | 222 | typdef struct blogentry 223 | { 224 | Node node; 225 | char *date; 226 | int number; 227 | char *title; 228 | char *class; 229 | char *author; 230 | size_t bsize; 231 | void *body; 232 | int comments; /* new field */ 233 | BlogEntry *entries; 234 | } *BlogEntry; 235 | 236 | Okay, and use the existing infrastructure for the comments. This means 237 | that we *can* have comments on comments, but we'll leave that for later. 238 | URL space wise, I might want to just limit the comments to individual days 239 | (I'm not trying for the Talmudic software yet). So we have: 240 | 241 | /2003/2/14/comments --illegal (return 404) 242 | /2003/2/14.1/comments --yes, all comments 243 | 244 | /2003/2/14.1/comments/1 -- first comment 245 | /2003/2/14.1/comments/1-3 -- first three comments 246 | 247 | Hmmm ... maybe ... 248 | 249 | /2003/2/14.1 -- just the entry 250 | /2003/2/14.1/ -- entry + return all comments? 251 | /2003/2/14.1/1 -- first comment 252 | /2003/2/14.1/1-3 -- first three comments 253 | 254 | /2003/2/14.1/1/ -- first comment + all 2l comments 255 | /2003/2/14.1/1/1 -- first 2l comment 256 | /2003/2/14.1/1/1-3 -- first three 2l comments 257 | 258 | I like this idea. Will have to expand the tumblers to support this, but 259 | that shouldn't be too difficult. 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | blog stuff: 269 | 270 | ... 271 | 272 | from http://www.tantek.com/log/2002/11.html#L20021124t1454 273 | 274 | added 275 | 276 | 277 | 278 | Talmud---the study 279 | Mishna (text) 280 | Gemara (commentaries on text) 281 | 282 | 283 | TODO: 284 | Return error upon requests for entries before 285 | first, or after current day. 286 | 287 | Fix local link code---if it starts with `/', then 288 | it's probably not under journal storage. 289 | 290 | + fix internal links for both entries and files 291 | + done 292 | 293 | 294 | NOTES: 295 | 296 | 2000/2/23 297 | 298 | Sean Conner 299 | 1 300 | blahblahblah 301 | yada yada yada 302 | 303 | blah blabh labha 304 | 305 | 306 | 2 307 | 308 | 309 | 310 | 311 | 312 | /logs/2000/2/21/extra.html 313 | 314 | /logs/2000/2/21.1/extra.jpg 315 | /log/2000/2/21.1 316 | /log/2000/2/21.2,4 317 | 318 | /log/2000/2/21,23-5 319 | 320 | 321 | 322 | 323 | (mod-blog 324 | 325 | (webdir "~www/sites/boston.conman.org/htdocs") 326 | (tempdir "~www/sites/boston.conman.org/journal") 327 | 328 | (name "The Boston Diaries") 329 | (url "http://boston.conman.org/") 330 | (start-date 1999/12/4) 331 | (author "Sean Conner") 332 | (email "sean@conman.org") 333 | 334 | (weblogcom true) 335 | 336 | (templates 337 | (html 338 | (template (+ tempdir "/html/regular/main")) 339 | (output (+ webdir "/index.html")) 340 | (entries 7d) 341 | (order reverse) 342 | ) 343 | (rss 344 | (template (+ tempdir "rss/main")) 345 | (output (+ webdir "/bostondiaries.rss")) 346 | (entries 15) 347 | (order reverse) 348 | ) 349 | (sidebar 350 | (template (+ tempdir "html/tabs/main")) 351 | (output (+ webdir "/boston.tab.html")) 352 | (entries 15) 353 | (order reverse) 354 | ) 355 | ) 356 | ) 357 | 358 | (email-notification 359 | (dir "~www/sites/boston.conman.org/notify") 360 | (list 361 | (email (+ dir "/db/email")) 362 | (optin (+ dir "/db/optin")) 363 | ) 364 | (error 365 | (client (+ dir "/error/400.html")) 366 | (server (+ dir "/error/500.html")) 367 | ) 368 | (page 369 | (pending (+ dir "/html/pending.html")) 370 | (subscribed (+ dir "/html/subscribed.html")) 371 | (verified (+ dir "/html/verified.html")) 372 | (~subscribed (+ dir "/html/not-subscribed.html")) 373 | (unsubscribed (+ dir "/html/unsubscribed.html")) 374 | ) 375 | (from "sean@conman.org") 376 | (reply-to "tbd-updates@conman.org") 377 | (subject "The Boston Diaries Test Update Notification") 378 | (message (+ dir "/mail/subscribe")) 379 | (bug (+ dir "/mail/bugreport")) 380 | (welcome (+ dir "/mail/welcome")) 381 | ) 382 | 383 | (system 384 | (cpu 600s) 385 | (mem 20m) 386 | (core 0) 387 | ) 388 | 389 | -------------------------------------------------------------------------------- /NOTES/notes.2: -------------------------------------------------------------------------------- 1 | 2 | 20000427.0000 spc 3 | 4 | oops, I can't really use a struct tm for a request field, as it 5 | doesn't have a field for which entry. For a hack I can use tm_hour 6 | to pass back that info temporarily. God this is turning into a 7 | large hack. 8 | 9 | 20000428.1948 spc 10 | 11 | What to do if the user requests 2000/4/3.8-6 when there are only 12 | four entries in 4/3? Or just the one? If just the one, return 404, 13 | but for a range? Redirect to a known advanced date is probably the 14 | Right Thing. Yet another thing to keep in mind. 15 | 16 | 20000831.0036 spc 17 | 18 | Had I made an unwarrented assumption? Just because I've done stuff 19 | like href="2000/3/4.2" doesn't mean I couldn't reference something 20 | like href="2000/3/4.2-7". So now, if the reference is ``onpage,'' 21 | then we can translate it to: 22 | 23 | href="#2000/3/4.2-7" 24 | 25 | Or should we? Shouldn't it be: 26 | 27 | href=#2000/3/4.2? 28 | 29 | Assuming we put in tags. But there could be 30 | overlap, say onpage is 2000/1/1-15 and inside is reference to 31 | 2000/1/13-18. There is overlap but it's not the entire item, 32 | so in that case, we shouldn't include an internal anchor link, 33 | but a full link. 34 | 35 | So, given two tumblers, we need to decide if one is inside 36 | another. Interesting problem ... It looks similar to line clipping, 37 | if you want to clip one dimentional lines. It might be prudent 38 | to calculate the Julian Day for each entry (make it fractional for 39 | the parts) so comparrisons can be done easier. 40 | 41 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | Finally, an update to the very outdated mod_blog that's been available since 3 | September 3, 2009. There still isn't much documentation because, as of yet. 4 | I doubt what's here is enough but for the most dedicated to go through the 5 | code to see how this works. 6 | 7 | REQUIREMENTS 8 | 9 | If you don't meet these requirements, then you probably won't get 10 | very far. The software was written in C (don't bother asking for 11 | Perl code---there isn't any, and there won't be any) and as such, 12 | makes certain assumptions, such as: 13 | 14 | * Linux (or any POSIX compilant UNIX system) 15 | * GCC (or any C99 compiler that may come with your system) 16 | * Apache 17 | * Lua 5.1 or higher 18 | 19 | The code right now assumes you'll be running this under Apache. 20 | 21 | COMPILATION 22 | 23 | You'll need to install my CGILIB library (6.6 or higher) to compile 24 | this blog software. You can install it from 25 | 26 | https://github.com/spc476/CGILib 27 | 28 | To install it once it's downloaded, all it should take is "make" and 29 | then "make install" as root. The library assumes a POSIX 30 | environment and should compile as is on most modern Unix systems. 31 | 32 | Once that is installed, you should be able to do a "make" and 33 | generate the blog engine. It's built as a setuid program to avoid 34 | having a world-writable directory, but if that is a concern, you can 35 | certain remove the setuid'ness of it, but make sure the data 36 | directories are writable by Apache. 37 | 38 | 39 | INSTALLATION 40 | 41 | The assumption on the blog engine is that the executable resides in 42 | a directory visible to Apache and runnable from within Apache's 43 | docroot for the the site in question. I have my copy named as 44 | "boston.cgi" and Apache configured to execute such programs via CGI. 45 | The htdocs/ subdirectory contains a sample website to run the blog. 46 | There's also a sample htaccess file that shows the method I use to 47 | run the blog engine. 48 | 49 | In the journal/ subdirectory is out-of-band files (which should not 50 | be served up by Apache). There you will find a sample configuration 51 | file and a sample entry to see how the data is stored (each entry is 52 | a separate file, stored under a Year/Month/Day directory structure). 53 | 54 | And yes, there is code to do email notifications, but it's not 55 | enabled by default. I only left the code in to support the half 56 | dozen people that did sign up to receive email notifications. 57 | 58 | The HTML entry form works, but it's not my primary method for new 59 | entries---for that, I use email. I have the following line in 60 | '/etc/aliases': 61 | 62 | myjournal: "/path/to/boston.cgi --config /path/to/config --email --cmd new" 63 | 64 | To make an entry, I format an email to the journal address as: 65 | 66 | +-----[top of file]---------------------------- 67 | |author: [who you set at author---exact match] 68 | |title: [title of entry] 69 | |class: [keywords] 70 | |status: [status repeated on Facebook] 71 | |adtag: [keyword used for advertising network] 72 | | 73 | |body of entry, usually including HTML 74 | | ... 75 | +---------------------------------------------- 76 | 77 | And that's pretty much it. 78 | 79 | Good luck. 80 | 81 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2007-08-28T18:40-0500 2 | 3 | Some quick notes on adding the archive section. Create 4 | "cmd=archive" with parameters "unit=MONTH|DAY". For "unit=MONTH", 5 | pick up the "date" parameter (should only be year). If not defined, 6 | list out each year, with each month listed and linked---otherwise, 7 | do just the year listed. for 'unit=DAY', 'date' should be YYYY/MM, 8 | go through, and for each day of the month, list out the title and 9 | link to the article. 10 | 11 | The robots META tags should read "noindex, follow". 12 | 13 | Also, it's not an archive section, it's an overview section. Rename 14 | the command appropriately. 15 | 16 | -------------------------------------------------------------------------------- /htdocs/about/index.html: -------------------------------------------------------------------------------- 1 | 2 |

About This Blog

3 | 4 |

Is it not nifty?

5 | 6 | -------------------------------------------------------------------------------- /htdocs/errors/403.html: -------------------------------------------------------------------------------- 1 | 2 |

You Do Not Have The Proper Clearance, Troubleshooter!

3 | 4 |

Only those of Ultraviolet can see this page.

5 | 6 | -------------------------------------------------------------------------------- /htdocs/errors/404.html: -------------------------------------------------------------------------------- 1 | 2 |

The Page has not been found

3 | 4 |

I'm sorry, but it's not here.

5 | 6 | -------------------------------------------------------------------------------- /htdocs/errors/500.html: -------------------------------------------------------------------------------- 1 | 2 |

Something is wrong with the server.

3 | 4 |

But if you are seeing this …

5 | 6 | -------------------------------------------------------------------------------- /htdocs/htaccess.sample: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | RewriteBase /blog/ 3 | 4 | RewriteRule ^([0-9][0-9])(.*) blog.cgi/$1$2 [L] 5 | 6 | ########################################################### 7 | # 8 | # You can also feed partial HTML files through the blog 9 | # engine. Any content there will appear as the main content 10 | # in the page. 11 | # 12 | ########################################################## 13 | 14 | RewriteRule ^about/$ blog.cgi/about/index.html [L] 15 | RewriteRule ^about/(.*) blog.cgi/about/$1 [L] 16 | 17 | ########################################################### 18 | # 19 | # You may want to password protect the following. You 20 | # can do this by limiting the POST method to require 21 | # authentication, or by protecting the location. 22 | # 23 | ########################################################### 24 | 25 | RewriteRule ^addentry.html$ blog.cgi?cmd=new [L] 26 | 27 | ########################################################### 28 | # 29 | # I also pump the error messages through the blogging engine 30 | # so they have the same look and feel 31 | # 32 | ############################################################ 33 | 34 | ErrorDocument 403 /blog/blog.cgi/errors/403.html?status=403 35 | ErrorDocument 404 /blog/blog.cgi/errors/404.html?status=404 36 | ErrorDocument 500 /blog/blog.cgi/errors/500.html?status=500 37 | 38 | -------------------------------------------------------------------------------- /journal/.first: -------------------------------------------------------------------------------- 1 | 2011/11/28.1 2 | -------------------------------------------------------------------------------- /journal/.last: -------------------------------------------------------------------------------- 1 | 2011/11/28.1 2 | -------------------------------------------------------------------------------- /journal/2011/11/28/1: -------------------------------------------------------------------------------- 1 |

Welcome to your new blog, and your very first blog entry, which you 2 | should probably delete.

3 | 4 |

The format is pretty simple—the entries are stored in flat files 5 | and are basically copied out in the appropriate place in the blog 6 | template.

7 | -------------------------------------------------------------------------------- /journal/2011/11/28/1.ad: -------------------------------------------------------------------------------- 1 | 2 |

This is an ad!

3 | 4 |

For only the low low price of 99¢ you too can buy 5 | ads on this blog! Just place the ad in 6 | entrynumber.ad and away you go!

7 | 8 | -------------------------------------------------------------------------------- /journal/2011/11/28/1.comments: -------------------------------------------------------------------------------- 1 | 2 |

A Comment!

3 | 4 |

Posted by Sean Conner

5 | 6 |

This is a comment. mod_blog doesn't support comments 7 | directly, but if there is a file with the name 8 | entrynumber.comments that file will be displayed along 9 | with the entry (placed accordindly to the template). You'll need an 10 | external script that can create such files though.

11 | 12 |
13 | 14 |

A Comment!

15 | 16 |

Posted by Sean Conner

17 | 18 |

As you can see, it's a very basic comment system that I haven't even 19 | bothered to implement!

20 | 21 |

Sigh.

22 | 23 |
24 | 25 | -------------------------------------------------------------------------------- /journal/2011/11/28/1.webmention: -------------------------------------------------------------------------------- 1 | http://www.example.net Some new blog someone wrote 2 | -------------------------------------------------------------------------------- /journal/2011/11/28/adtag: -------------------------------------------------------------------------------- 1 | web development 2 | -------------------------------------------------------------------------------- /journal/2011/11/28/authors: -------------------------------------------------------------------------------- 1 | Joe Blog 2 | -------------------------------------------------------------------------------- /journal/2011/11/28/class: -------------------------------------------------------------------------------- 1 | blogging, first, first post 2 | -------------------------------------------------------------------------------- /journal/2011/11/28/status: -------------------------------------------------------------------------------- 1 | I just made a new blog post 2 | -------------------------------------------------------------------------------- /journal/2011/11/28/titles: -------------------------------------------------------------------------------- 1 | My New Blog—Is it not nifty 2 | -------------------------------------------------------------------------------- /journal/apache.conf: -------------------------------------------------------------------------------- 1 | 2 | # ============== 3 | # Sample Apache 2.4 configuration, and will have to be changed to match your 4 | # setup. If anything here is unclear, check Apache documentation at 5 | # 6 | # https://httpd.apache.org/docs/2.4/ 7 | # 8 | # ================ 9 | 10 | 11 | ServerName www.example.com 12 | ServerAdmin joe@example.com 13 | DocumentRoot /www/example.com/htdocs 14 | AddHandler cgi-script .cgi 15 | 16 | # ---------------------------- 17 | # This assumes you renamed the exectuable to 'htdocs/blog/blog.cgi'. The 18 | # actual name doesn't really matter, as long as you have the Rewrite rules 19 | # set up to match the actual name. 20 | # 21 | # The following sets the environment variable BLOG_CONFIG to point to the 22 | # configuration file whenever Apache runs blog.cgi. 23 | # --------------- 24 | 25 | 26 | SetEnv BLOG_CONFIG /www/example.com/blog.conf 27 | 28 | 29 | 30 | Options All 31 | AllowOverride None 32 | Require all granted 33 | 34 | # ---------------------- 35 | # It's a good idea to protect the web interface behind some form of 36 | # authentication. Here, we're protecting the POST method to require 37 | # authentication. 38 | # ---------------------- 39 | 40 | AuthType Basic 41 | AuthName "A Blog Grows in Cyberspace" 42 | AuthUserFile /www/example.com/users 43 | AuthGroupFile /www/example.com/groups 44 | 45 | 46 | Require valid-user 47 | 48 | 49 | # ----------------------- 50 | # The use of the RewriteEngine in Apache is to hide the fact that we're 51 | # generating the data with a CGI script. This isn't strictly necessary, 52 | # but I find it produces much nicer looking URLs. For our sample blog, 53 | # a given entry will have the URL of: 54 | # 55 | # http://www.example.com/blog/2011/11/28.1 56 | # 57 | # You can also run pages through the blog engine to produce output that 58 | # matches the blog, since the file contents will be run through the 59 | # templating engine. 60 | # ---------------------- 61 | 62 | RewriteEngine on 63 | RewriteBase /blog/ 64 | 65 | RewriteRule ^([0-9][0-9])(.*) boston.cgi/$1$2 [L] 66 | 67 | RewriteRule ^about/$ boston.cgi/about/index.html [L] 68 | RewriteRule ^about/(.*) boston.cgi/about/$1 [L] 69 | RewriteRule ^addentry.html$ boston.cgi?cmd=new [L] 70 | RewriteRule ^today$ boston.cgi?cmd=today [L] 71 | RewriteRule ^(today)/(.*) boston.cgi?cmd=today&path=$1&day=$2 [L] 72 | 73 | 74 | -------------------------------------------------------------------------------- /journal/atom/categories: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /journal/atom/entry: -------------------------------------------------------------------------------- 1 | 2 | tag:www.example.net/blog,%{entry.id}%:%{entry.url}% 3 | %{entry.title}% 4 | %{entry.pubdatetime}% 5 | 6 | %{atom.categories}% 7 | %{entry.body.entified}% 8 | 9 | 10 | -------------------------------------------------------------------------------- /journal/atom/main: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %{blog.name}% 5 | %{update.time}% 6 | %{blog.url.home}% 7 | 8 | 15 | 16 | 23 | 24 | 25 | %{blog.author}% 26 | %{blog.author.email}% 27 | %{blog.url.home}% 28 | 29 | 30 | mod_blog 31 | © %{begin.year}%-%{now.year}% by %{blog.author}%. All Rights Reserved 32 | 33 | %{atom.entry}% 34 | 35 | 36 | -------------------------------------------------------------------------------- /journal/blog.conf: -------------------------------------------------------------------------------- 1 | -- luacheck: globals name description class basedir webdir lockfile 2 | -- luacheck: globals url adtag conversion author templates affiliate 3 | -- luacheck: ignore 611 4 | 5 | -- ************************************************************************ 6 | 7 | -- You get a full Lua environment when loading this file. This means you 8 | -- can do things like: 9 | -- 10 | -- os.setlocale("se_NO.UTF-8") 11 | -- basedir = os.getenv("HOME") .. "/source/boston/journal" 12 | -- webdir = os.getenv("HOME") .. "/source/boston/htdocs" 13 | -- 14 | -- Or more. 15 | -- 16 | -- ------------------------------------------------------------------------ 17 | -- 18 | -- This sample file lists all the possible fields that can be configured, 19 | -- with their default value (unless otherwise noted). 20 | -- 21 | -- name - title of the blog 22 | -- description - description of blog 23 | -- class - default keywords for blog 24 | -- basedir - location of journal entries 25 | -- webdir - location of web-accessible directory 26 | -- lockfile - lockfile (in case of multiple submissions at once) 27 | -- url - URL of blog, MUST end with a '/' 28 | -- adtag - default advertising keyword 29 | -- conversion - input conversion ('html', 'mixed', 'text' , 'none') 30 | -- prehook - script to run before adding an entry 31 | -- argumnents: 32 | -- [1] body filename 33 | -- [2] meta information filename 34 | -- return: 35 | -- == 0 okay to add entry 36 | -- != 0 do not add entry 37 | -- posthook - script to run after adding an entry 38 | -- arguments: 39 | -- [1] URL of resulting post 40 | -- 41 | -- ************************************************************************ 42 | 43 | name = "A Blog Grows in Cyberspace" 44 | description = "A place where I talk about stuff in cyperspace." 45 | class = "blog, rants, random stuff, programming" 46 | basedir = "." -- use full path for best results 47 | webdir = "htdocs" 48 | lockfile = ".modblog.lock" 49 | url = "http://www.example.com/blog/" 50 | adtag = "programming" 51 | conversion = "html" 52 | -- prehook = "./prehook_script" -- no default 53 | -- posthook = "./posthook_script" -- no default 54 | 55 | -- ************************************************************************ 56 | -- 57 | -- Define the default (only?) author of the blog 58 | -- 59 | -- name - name of author 60 | -- email - email address of author 61 | -- file - if multiple authors, this is the location of a text file 62 | -- with colon separated fields specifying alternative authors 63 | -- who are permitted to post. 64 | -- fields - which fields of the given file contain the following info: 65 | -- uid - user id 66 | -- name - name 67 | -- email - email address 68 | -- 69 | -- If 'file' and 'fields' are not defined, then only the default author is 70 | -- allowed to post. The default value for 'fields' assumes an Apache style 71 | -- htpasswd file. 72 | -- 73 | -- ************************************************************************ 74 | 75 | author = 76 | { 77 | name = "Joe Blog" , 78 | email = "joe@example.com", 79 | -- file = "users", -- no default 80 | -- fields = { uid = 0 , name = 3 , email = 4 }, 81 | } 82 | 83 | -- ************************************************************************ 84 | -- 85 | -- Email notification 86 | -- 87 | -- There are no defaults for any of this information, and to even use it, 88 | -- you'll need a tool that isn't provided by this repository. Even I barely 89 | -- use it, having only three people signed up. I found out years ago that 90 | -- having a form to fill out is useless as spammers will spam the form even 91 | -- when there's nothing to gain from it. You are on your own if you want to 92 | -- use email notification. But just in case you can engineer how this is 93 | -- supposed to work: 94 | -- 95 | -- list - databse of email addresses 96 | -- message - text file of email (template based) 97 | -- subject - subject line 98 | -- 99 | -- ************************************************************************ 100 | 101 | -- email = 102 | -- { 103 | -- list = "./notify/email.db", -- no default 104 | -- message = "./notify/message.txt", -- no default 105 | -- subject = name .. " Update Notification", -- no default 106 | -- } 107 | 108 | -- ************************************************************************ 109 | -- 110 | -- Templates, or "How to generate some output" 111 | -- 112 | -- This is mandatory, and at least one template must be defined. The two 113 | -- mandatory fields are 'template' and 'output'. 'items' defaults to 15 and 114 | -- reverse to 'false'. posthook is optional with no default value. 115 | -- 116 | -- template - template directory 117 | -- output - output file 118 | -- items - how many items to output. If this is a number, then 119 | -- it's now many posts to include. If it's a string, 120 | -- it's the number of days worth of entries to include. 121 | -- An optional suffix can be include: 'd' for days, 122 | -- 'w' for weeks, or 'm' for month (30 days). 123 | -- reverse - if true, display entries in reverse chronological order 124 | -- posthook - a script to run once the template has been generated. 125 | -- arguments: 126 | -- [1] output file name 127 | -- [2] "new" if adding a new post 128 | -- "regenerate" if just regenerating the file 129 | -- 130 | -- ************************************************************************ 131 | 132 | templates = 133 | { 134 | { 135 | template = "html", 136 | output = webdir .. "/index.html", 137 | items = "7d", 138 | reverse = true, 139 | -- posthook = "posthook_template_script" -- no default 140 | }, 141 | 142 | { 143 | template = "rss", 144 | output = webdir .. "/index.rss", 145 | items = 15, 146 | reverse = true, 147 | }, 148 | 149 | { 150 | template = "atom", 151 | output = webdir .. "/index.atom", 152 | items = 15, 153 | reverse = true, 154 | }, 155 | 156 | { 157 | template = "json", 158 | output = webdir .. "/index.json", 159 | items = 15, 160 | reverse = true, 161 | } 162 | } 163 | 164 | -- ************************************************************************ 165 | -- 166 | -- Affiliate links, or rather, a shorthand for specifying URLs. For 167 | -- example: 'xkcd:795' will generate the link 'https://xkcd.com/795'. No 168 | -- default values here, and this entire block is optional. 169 | -- 170 | -- ************************************************************************ 171 | 172 | affiliate = 173 | { 174 | { 175 | proto = "asin", 176 | link = "http://www.amazon.com/exec/obidos/ASIN/%s/conmanlaborat-20" 177 | }, 178 | 179 | -- ---------------------------------------------------------- 180 | -- based upon https://news.ycombinator.com/item?id=12631664 181 | -- ---------------------------------------------------------- 182 | 183 | { 184 | proto = "xkcd", 185 | link = "https://xkcd.com/%s" 186 | }, 187 | } 188 | -------------------------------------------------------------------------------- /journal/html/ad: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /journal/html/blog.body: -------------------------------------------------------------------------------- 1 | 2 |

%{blog.date.normal}%

3 | 4 | %{entry}% 5 | 6 | 7 | -------------------------------------------------------------------------------- /journal/html/comments: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | %{comments.check}% 5 | 6 | %{comments.body}% 7 | 8 |
9 | 10 |
11 | Comment? Venting? Go ahead … 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 |
35 | -------------------------------------------------------------------------------- /journal/html/cond.hr: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /journal/html/edit: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 |
Filter
16 |
17 |
 
 
 
22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 | -------------------------------------------------------------------------------- /journal/html/entry: -------------------------------------------------------------------------------- 1 | %{entry.cond.date}% 2 | %{cond.hr}% 3 |

%{entry.title}%

4 | %{entry.cond.author}% 5 | 6 | 7 | %{entry.body}% 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /journal/html/entry.cond.author: -------------------------------------------------------------------------------- 1 |

by %{entry.cond.author}%

2 | -------------------------------------------------------------------------------- /journal/html/entry.cond.date: -------------------------------------------------------------------------------- 1 |

%{date.day.normal}%

2 | -------------------------------------------------------------------------------- /journal/html/main: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | %{navigation.link}% 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | %{blog.title}%%{blog.name}% 25 | 26 | 27 | 28 | 29 | 30 |
31 |

%{blog.name}%

32 |

%{blog.description}%

33 |
34 | 35 | 36 | 37 |
38 | %{edit}% 39 | %{entry}% 40 |
41 | 42 | 43 | 44 | 54 | 55 | 56 | 57 |
58 | 59 |

Copyright © %{begin.year}%-%{now.year}% by %{blog.author}%. All Rights 61 | Reserved.

62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /journal/html/navigation.bar: -------------------------------------------------------------------------------- 1 | %{navigation.bar.next}% 2 | %{navigation.bar.prev}% 3 | -------------------------------------------------------------------------------- /journal/html/navigation.bar.next: -------------------------------------------------------------------------------- 1 |
  • Next
  • 2 | -------------------------------------------------------------------------------- /journal/html/navigation.bar.prev: -------------------------------------------------------------------------------- 1 |
  • Previous
  • 2 | -------------------------------------------------------------------------------- /journal/html/navigation.current: -------------------------------------------------------------------------------- 1 |
  • Current
  • 2 | -------------------------------------------------------------------------------- /journal/html/navigation.link: -------------------------------------------------------------------------------- 1 | %{navigation.link.next}% 2 | %{navigation.link.prev}% 3 | -------------------------------------------------------------------------------- /journal/html/navigation.link.next: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /journal/html/navigation.link.prev: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /journal/html/webmention: -------------------------------------------------------------------------------- 1 |

    Webmention

    2 |
      3 | %{webmention.item}% 4 |
    5 | -------------------------------------------------------------------------------- /journal/html/webmention.item: -------------------------------------------------------------------------------- 1 |
  • %{webmention.title}%
  • 2 | -------------------------------------------------------------------------------- /journal/json/item: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "%{rss.item.url}%", 3 | "url" : "%{rss.item.url}%", 4 | "title" : "%{entry.title}%", 5 | "content_html" : "%{entry.body.jsonified}%", 6 | "date_published" : "%{entry.pubdatetime}%", 7 | "authors" : [ { "name" : "%{entry.author}%" } ], 8 | "tags" : [ %{entry.class.jsonified}% ], 9 | "language" : "en-US" 10 | } 11 | -------------------------------------------------------------------------------- /journal/json/main: -------------------------------------------------------------------------------- 1 | { 2 | "version" : "https://jsonfeed.org/version/1,1", 3 | "title" : "%{blog.name}%", 4 | "home_page_url" : "%{blog.url.home}%", 5 | "feed_url" : "%{blog.url.home}%/index.json", 6 | "description" : "%{blog.description}%", 7 | "authors" : [ { "name" : "%{blog.author}%" } ], 8 | "language" : "en-US", 9 | "items" : [ %{json.item}% ] 10 | } 11 | -------------------------------------------------------------------------------- /journal/posthook_script: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script that is run after an entry is successfully added to 4 | # the blog. This could be used to notify some other service that the blog 5 | # has a new entry. 6 | # 7 | # $1 will be the URL of the newly added entry 8 | 9 | # For example, let's send ourselves an email when a new entry is added. 10 | 11 | sendmail </dev/null 2>/dev/null 26 | then 27 | exit 1 28 | fi 29 | 30 | # Now check that the entry contains valid UTF-8 characters. A more 31 | # ambitious script might validate the contents in some way. But as an 32 | # example, this is fine. 33 | 34 | if ! iconv -f UTF-8 -t UTF-8 $1 -o /dev/null >/dev/null 2>/dev/null 35 | then 36 | exit 1 37 | fi 38 | 39 | # Everything seems okay 40 | 41 | exit 0 42 | -------------------------------------------------------------------------------- /journal/rss/item: -------------------------------------------------------------------------------- 1 | 2 | %{entry.title}% 3 | 4 | %{rss.item.url}% 5 | 6 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /journal/rss/main: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %{blog.name}% 5 | %{rss.url}% 6 | %{blog.description}% 7 | en-us 8 | %{rss.pubdate}% 9 | %{blog.author.email}% (%{blog.author}%) 10 | %{blog.author.email}% (%{blog.author}%) 11 | 12 | Copyright %{begin.year}%-%{now.year}% by %{blog.author}%. All Rights Reserved. 13 | 14 | 15 | %{rss.item}% 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/addutil.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * 3 | * Copyright 2001 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *********************************************************************/ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | #include "conversion.h" 35 | #include "backend.h" 36 | #include "blogutil.h" 37 | 38 | #define DB_BLOCK 1024 39 | 40 | /*********************************************************************/ 41 | 42 | bool entry_add(Blog *blog,Request *req) 43 | { 44 | BlogEntry *entry; 45 | bool rc = true; 46 | 47 | assert(blog != NULL); 48 | assert(req != NULL); 49 | 50 | fix_entry(req); 51 | 52 | if (blog->config.prehook != NULL) 53 | { 54 | FILE *fp; 55 | char fnbody[L_tmpnam]; 56 | char fnmeta[L_tmpnam]; 57 | 58 | tmpnam(fnbody); 59 | tmpnam(fnmeta); 60 | 61 | fp = fopen(fnbody,"w"); 62 | if (fp != NULL) 63 | { 64 | fputs(req->body,fp); 65 | fclose(fp); 66 | } 67 | else 68 | { 69 | syslog(LOG_ERR,"entry_add: tmp-body: %s",strerror(errno)); 70 | return false; 71 | } 72 | 73 | fp = fopen(fnmeta,"w"); 74 | if (fp != NULL) 75 | { 76 | char ts[12]; 77 | 78 | if (req->date == NULL) 79 | snprintf(ts,sizeof(ts),"%04d/%02d/%02d",blog->now.year,blog->now.month,blog->now.day); 80 | else 81 | snprintf(ts,sizeof(ts),"%s",req->date); 82 | 83 | fprintf( 84 | fp, 85 | "Author: %s\n" 86 | "Title: %s\n" 87 | "Class: %s\n" 88 | "Status: %s\n" 89 | "Date: %s\n" 90 | "Adtag: %s\n" 91 | "\n", 92 | req->author, 93 | req->title, 94 | req->class, 95 | req->status, 96 | ts, 97 | req->adtag 98 | ); 99 | fclose(fp); 100 | } 101 | else 102 | { 103 | remove(fnbody); 104 | syslog(LOG_ERR,"entry_add: tmp-meta: %s",strerror(errno)); 105 | return false; 106 | } 107 | 108 | rc = run_hook("entry-pre-hook",(char const *[]){ blog->config.prehook , fnbody , fnmeta , NULL }); 109 | 110 | remove(fnmeta); 111 | remove(fnbody); 112 | 113 | if (!rc) 114 | return rc; 115 | } 116 | 117 | entry = BlogEntryNew(blog); 118 | 119 | if (emptynull_string(req->date)) 120 | entry->when = blog->now; 121 | else 122 | { 123 | char *p; 124 | 125 | entry->when.year = strtoul(req->date,&p,10); p++; 126 | entry->when.month = strtoul(p, &p,10); p++; 127 | entry->when.day = strtoul(p, &p,10); 128 | } 129 | 130 | entry->when.part = 0; 131 | entry->timestamp = blog->tnow; 132 | entry->title = req->title; 133 | entry->class = req->class; 134 | entry->status = req->status; 135 | entry->author = req->author; 136 | entry->adtag = req->adtag; 137 | entry->body = req->body; 138 | 139 | BlogEntryWrite(entry); 140 | 141 | req->when = entry->when; 142 | 143 | if (blog->config.posthook != NULL) 144 | { 145 | /*--------------------------------------------------------------- 146 | ; XXX maybe in the future move email notifications to this hook? 147 | ;----------------------------------------------------------------*/ 148 | 149 | char url[1024]; 150 | 151 | snprintf( 152 | url, 153 | sizeof(url), 154 | "%s/%04d/%02d/%02d.%d", 155 | blog->config.url, 156 | entry->when.year, 157 | entry->when.month, 158 | entry->when.day, 159 | entry->when.part 160 | ); 161 | 162 | rc = run_hook("entry-post-hook",(char const *[]){ blog->config.posthook , url , NULL }); 163 | } 164 | 165 | if (req->f.email) 166 | notify_emaillist(entry); 167 | 168 | free(entry); 169 | return rc; 170 | } 171 | 172 | /************************************************************************/ 173 | 174 | void fix_entry(Request *req) 175 | { 176 | FILE *out; 177 | FILE *in; 178 | char *tmp; 179 | size_t size; 180 | 181 | assert(req != NULL); 182 | 183 | /*--------------------------------- 184 | ; convert the title 185 | ;--------------------------------*/ 186 | 187 | if (!empty_string(req->title)) 188 | { 189 | tmp = NULL; 190 | size = 0; 191 | out = open_memstream(&tmp,&size); 192 | in = fmemopen(req->title,strlen(req->title),"r"); 193 | 194 | buff_conversion(in,out); 195 | fclose(in); 196 | fclose(out); 197 | free(req->title); 198 | req->title = entity_conversion(tmp); 199 | free(tmp); 200 | } 201 | 202 | /*-------------------------------------- 203 | ; convert the status 204 | ;-------------------------------------*/ 205 | 206 | if (!empty_string(req->status)) 207 | { 208 | tmp = NULL; 209 | size = 0; 210 | out = open_memstream(&tmp,&size); 211 | in = fmemopen(req->status,strlen(req->status),"r"); 212 | 213 | buff_conversion(in,out); 214 | fclose(in); 215 | fclose(out); 216 | free(req->status); 217 | req->status = entity_conversion(tmp); 218 | free(tmp); 219 | } 220 | 221 | /*------------------------------------- 222 | ; convert body 223 | ;--------------------------------------*/ 224 | 225 | if (!empty_string(req->body)) 226 | { 227 | tmp = NULL; 228 | size = 0; 229 | out = open_memstream(&tmp,&size); 230 | in = fmemopen(req->body,strlen(req->body),"r"); 231 | 232 | (*req->conversion)(in,out); 233 | fclose(in); 234 | fclose(out); 235 | free(req->body); 236 | req->body = tmp; 237 | } 238 | } 239 | 240 | /*************************************************************************/ 241 | 242 | static void dbcritical(char const *msg) 243 | { 244 | assert(msg != NULL); 245 | syslog(LOG_ERR,"gdbm_open() = %s",msg); 246 | } 247 | 248 | /************************************************************************/ 249 | 250 | static ssize_t utf8_read(void *cookie,char *buffer,size_t bytes) 251 | { 252 | FILE *realin = cookie; 253 | size_t s = 0; 254 | 255 | assert(buffer != NULL); 256 | 257 | if (feof(realin)) 258 | return 0; 259 | 260 | while(bytes) 261 | { 262 | int c = fgetc(realin); 263 | if (c == EOF) return s; 264 | 265 | if (c == '&') 266 | { 267 | char numbuf[10]; 268 | 269 | if (bytes < 4) 270 | { 271 | ungetc(c,realin); 272 | return s; 273 | } 274 | 275 | c = fgetc(realin); 276 | assert(c == '#'); 277 | 278 | for (size_t i = 0 ; i < sizeof(numbuf) ; i++) 279 | { 280 | c = fgetc(realin); 281 | if (!isdigit(c)) 282 | { 283 | assert(c == ';'); 284 | numbuf[i] = '\0'; 285 | break; 286 | } 287 | numbuf[i] = c; 288 | } 289 | 290 | unsigned long val = strtoul(numbuf,NULL,10); 291 | assert(val <= 0x10FFFFuL); 292 | 293 | if (val < 0x80uL) 294 | { 295 | *buffer++ = (unsigned char)val; 296 | bytes--; 297 | s++; 298 | } 299 | else if (val < 0x800uL) 300 | { 301 | *(unsigned char *)buffer++ = (unsigned char)((val >> 6) | 0xC0uL); 302 | *(unsigned char *)buffer++ = (unsigned char)((val & 0x3F) | 0x80uL); 303 | bytes -= 2; 304 | s += 2; 305 | } 306 | else if (val < 0x10000uL) 307 | { 308 | *(unsigned char *)buffer++ = (unsigned char)(((val >> 12) ) | 0xE0uL); 309 | *(unsigned char *)buffer++ = (unsigned char)(((val >> 6) & 0x3FuL) | 0x80uL); 310 | *(unsigned char *)buffer++ = (unsigned char)(((val ) & 0x3FuL) | 0x80uL); 311 | bytes -= 3; 312 | s += 3; 313 | } 314 | else if (val < 0x200000uL) 315 | { 316 | *(unsigned char *)buffer++ = (unsigned char)(((val >> 18) ) | 0xF0uL); 317 | *(unsigned char *)buffer++ = (unsigned char)(((val >> 12) & 0x3FuL) | 0x80uL); 318 | *(unsigned char *)buffer++ = (unsigned char)(((val >> 6) & 0x3FuL) | 0x80uL); 319 | *(unsigned char *)buffer++ = (unsigned char)(((val ) & 0x3Ful) | 0x80uL); 320 | bytes -= 4; 321 | s += 4; 322 | } 323 | else 324 | assert(0); 325 | } 326 | else 327 | { 328 | *buffer++ = c; 329 | bytes--; 330 | s++; 331 | } 332 | } 333 | 334 | return s; 335 | } 336 | 337 | /************************************************************************/ 338 | 339 | struct qpcookie 340 | { 341 | FILE *fpin; 342 | size_t l; 343 | }; 344 | 345 | static ssize_t qp_read(void *cookie,char *buffer,size_t bytes) 346 | { 347 | struct qpcookie *qpc = cookie; 348 | FILE *realin = qpc->fpin; 349 | size_t s = 0; 350 | 351 | assert(buffer != NULL); 352 | 353 | if (feof(realin)) 354 | return 0; 355 | 356 | while(bytes) 357 | { 358 | if (qpc->l >= 68) 359 | { 360 | if (bytes < 2) return s; 361 | *buffer++ = '='; 362 | *buffer++ = '\n'; 363 | s += 2; 364 | bytes -= 2; 365 | qpc->l = 0; 366 | continue; 367 | } 368 | 369 | int c = fgetc(realin); 370 | if (c == EOF) return s; 371 | 372 | if ((c == '=') || (c > '~')) 373 | { 374 | if (bytes < 3) 375 | { 376 | ungetc(c,realin); 377 | return s; 378 | } 379 | 380 | sprintf(buffer,"=%02X",c); 381 | buffer += 3; 382 | bytes -= 3; 383 | s += 3; 384 | qpc->l += 3; 385 | } 386 | else 387 | { 388 | *buffer++ = c; 389 | bytes--; 390 | s++; 391 | qpc->l++; 392 | } 393 | } 394 | 395 | return s; 396 | } 397 | 398 | /************************************************************************/ 399 | 400 | static void cb_email_title(FILE *out,void *data) 401 | { 402 | BlogEntry *entry = data; 403 | 404 | assert(out != NULL); 405 | assert(data != NULL); 406 | 407 | if (!empty_string(entry->status)) 408 | fprintf(out,"%s",entry->status); 409 | else 410 | fprintf(out,"%s",entry->title); 411 | } 412 | 413 | /*************************************************************************/ 414 | 415 | static void cb_email_url(FILE *out,void *data) 416 | { 417 | BlogEntry *entry = data; 418 | 419 | assert(out != NULL); 420 | assert(data != NULL); 421 | 422 | fprintf( 423 | out, 424 | "%s%04d/%02d/%02d.%d", 425 | entry->blog->config.url, 426 | entry->when.year, 427 | entry->when.month, 428 | entry->when.day, 429 | entry->when.part 430 | ); 431 | } 432 | 433 | /*************************************************************************/ 434 | 435 | static void cb_email_author(FILE *out,void *data) 436 | { 437 | BlogEntry *entry = data; 438 | 439 | assert(out != NULL); 440 | assert(data != NULL); 441 | fprintf(out,"%s",entry->author); 442 | } 443 | 444 | /*************************************************************************/ 445 | 446 | void notify_emaillist(BlogEntry *entry) 447 | { 448 | static struct chunk_callback const emcallbacks[] = 449 | { 450 | { "email.title" , cb_email_title } , 451 | { "email.url" , cb_email_url } , 452 | { "email.author" , cb_email_author } , 453 | }; 454 | 455 | GDBM_FILE list; 456 | Chunk templates; 457 | Email email; 458 | FILE *out; 459 | FILE *in; 460 | char *tmp = NULL; 461 | size_t size = 0; 462 | 463 | assert(entry != NULL); 464 | assert(entry->valid); 465 | 466 | list = gdbm_open((char *)entry->blog->config.email.list,DB_BLOCK,GDBM_READER,0,dbcritical); 467 | if (list == NULL) 468 | return; 469 | 470 | email = EmailNew(); 471 | email->from = strdup(entry->blog->config.author.email); 472 | email->subject = strdup(entry->blog->config.email.subject); 473 | 474 | PairListCreate(&email->headers,"MIME-Version","1.0"); 475 | PairListCreate(&email->headers,"Content-Type","text/plain; charset=UTF-8; format=flowed"); 476 | PairListCreate(&email->headers,"Content-Transfer-Encoding","quoted-printable"); 477 | 478 | templates = ChunkNew("",emcallbacks,sizeof(emcallbacks) / sizeof(emcallbacks[0])); 479 | out = open_memstream(&tmp,&size); 480 | ChunkProcess(templates,entry->blog->config.email.message,out,entry); 481 | ChunkFree(templates); 482 | fclose(out); 483 | 484 | in = fmemopen(tmp,size,"r"); 485 | if (in != NULL) 486 | { 487 | FILE *inutf8 = fopencookie(in,"r",(cookie_io_functions_t) 488 | { 489 | utf8_read, 490 | NULL, 491 | NULL, 492 | NULL 493 | }); 494 | if (inutf8 != NULL) 495 | { 496 | struct qpcookie qpc; 497 | 498 | qpc.l = 0; 499 | qpc.fpin = inutf8; 500 | FILE *inqp = fopencookie(&qpc,"r",(cookie_io_functions_t) 501 | { 502 | qp_read, 503 | NULL, 504 | NULL, 505 | NULL 506 | }); 507 | if (inqp != NULL) 508 | { 509 | datum key; 510 | datum nextkey; 511 | 512 | fcopy(email->body,inqp); 513 | key = gdbm_firstkey(list); 514 | 515 | while(key.dptr != NULL) 516 | { 517 | datum content = gdbm_fetch(list,key); 518 | if (content.dptr != NULL) 519 | { 520 | email->to = content.dptr; 521 | EmailSend(email); 522 | free(content.dptr); 523 | } 524 | 525 | nextkey = gdbm_nextkey(list,key); 526 | free(key.dptr); 527 | key = nextkey; 528 | } 529 | fclose(inqp); 530 | } 531 | fclose(inutf8); 532 | } 533 | fclose(in); 534 | } 535 | 536 | email->to = NULL; 537 | EmailFree(email); 538 | gdbm_close(list); 539 | free(tmp); 540 | } 541 | 542 | /*************************************************************************/ 543 | -------------------------------------------------------------------------------- /src/authenticate.c: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * 3 | * Copyright 2005 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *************************************************************************/ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include "frontend.h" 31 | 32 | /*************************************************************************/ 33 | 34 | char *get_remote_user(void) 35 | { 36 | /*------------------------------------------------------- 37 | ; A change in Apache 2.0.48: 38 | ; Remember an authenticated user during internal redirects if the 39 | ; redirection target is not access protected and pass it to scripts 40 | ; using the REDIRECT_REMOTE_USER environment variable. PR 10678, 41 | ; 11602 42 | ; 43 | ; Because of the way I'm doing this, this is affecting me, so I need to 44 | ; check both REMOTE_USER and REDIRECT_REMOTE_USER. 45 | ;------------------------------------------------------*/ 46 | 47 | char const *name = getenv("REMOTE_USER"); 48 | 49 | if (name == NULL) 50 | { 51 | name = getenv("REDIRECT_REMOTE_USER"); 52 | if (name == NULL) 53 | name = ""; 54 | } 55 | 56 | return strdup(name); 57 | } 58 | 59 | /************************************************************************/ 60 | 61 | static size_t breakline(char *dest[],size_t dsize,FILE *in) 62 | { 63 | char *line = NULL; 64 | char *p; 65 | char *colon; 66 | size_t cnt; 67 | size_t size = 0; 68 | ssize_t rc; 69 | 70 | assert(dest != NULL); 71 | assert(dsize > 0); 72 | assert(in != NULL); 73 | 74 | rc = getline(&line,&size,in); 75 | if ((rc == -1) || (emptynull_string(line))) 76 | { 77 | free(line); 78 | return 0; 79 | } 80 | 81 | if (line[rc - 1] == '\n') 82 | line[rc - 1] = '\0'; 83 | 84 | p = line; 85 | cnt = 0; 86 | 87 | do 88 | { 89 | dest[cnt] = p; 90 | colon = strchr(p,':'); 91 | if (colon != NULL) 92 | { 93 | *colon = '\0'; 94 | p = colon + 1; 95 | } 96 | cnt++; 97 | } while ((colon != NULL) && (cnt < dsize)); 98 | 99 | return cnt; 100 | } 101 | 102 | /************************************************************************/ 103 | 104 | bool authenticate_author(Blog const *blog,Request *req) 105 | { 106 | FILE *in; 107 | char *lines[10]; 108 | size_t cnt; 109 | 110 | assert(blog != NULL); 111 | assert(req != NULL); 112 | assert(req->author != NULL); 113 | 114 | if (blog->config.author.file == NULL) 115 | return strcmp(req->author,blog->config.author.name) == 0; 116 | 117 | in = fopen(blog->config.author.file,"r"); 118 | if (in == NULL) 119 | { 120 | syslog(LOG_ERR,"%s: %s",blog->config.author.file,strerror(errno)); 121 | return false; 122 | } 123 | 124 | while((cnt = breakline(lines,10,in))) 125 | { 126 | if ((blog->config.author.fields.uid < cnt) && (strcmp(req->author,lines[blog->config.author.fields.uid]) == 0)) 127 | { 128 | if (blog->config.author.fields.name < cnt) 129 | { 130 | free(req->author); 131 | req->author = strdup(lines[blog->config.author.fields.name]); 132 | fclose(in); 133 | free(lines[0]); /* see comment below */ 134 | return true; 135 | } 136 | } 137 | 138 | /*------------------------------------------------------------------ 139 | ; Some tight coupling between this routine and breakline(). The first 140 | ; element of lines[] needs to be freed, but not the rest. 141 | ;--------------------------------------------------------------------*/ 142 | 143 | free(lines[0]); 144 | } 145 | 146 | fclose(in); 147 | return false; 148 | } 149 | 150 | /**************************************************************************/ 151 | -------------------------------------------------------------------------------- /src/backend.h: -------------------------------------------------------------------------------- 1 | /*********************************************** 2 | * 3 | * Copyright 2011 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | ************************************************/ 22 | 23 | #ifndef I_5B8ED10A_F8F4_5F83_A7EA_CD76EE7A05D8 24 | #define I_5B8ED10A_F8F4_5F83_A7EA_CD76EE7A05D8 25 | 26 | #include 27 | 28 | #include "frontend.h" 29 | #include "blog.h" 30 | #include "timeutil.h" 31 | #include "wbtum.h" 32 | 33 | typedef int (*pagegen__f)(Blog *,Request *,struct template const *,FILE *); 34 | 35 | struct callback_data 36 | { 37 | List list; 38 | BlogEntry *entry; /* current entry being processed */ 39 | FILE *ad; /* file containing ad */ 40 | char *adtag; 41 | char *adcat; 42 | FILE *wm; /* file containing webmentions */ 43 | char *wmtitle; /* webmention title */ 44 | char *wmurl; /* webmention url */ 45 | struct btm last; /* timestamp of previous entry */ 46 | struct btm previous; 47 | struct btm next; 48 | unit__e navunit; 49 | http__e status; 50 | template__t const *template; 51 | Request const *request; 52 | Blog *blog; 53 | }; 54 | 55 | /************************************************/ 56 | 57 | extern struct callback_data *callback_init (struct callback_data *,Blog *,Request const *); 58 | extern pagegen__f TO_pagegen (char const *); 59 | extern int generate_thisday (Blog *,Request *,FILE *,struct btm); 60 | extern int generate_pages (Blog *,Request *); 61 | extern int pagegen_items (Blog *,Request *,template__t const *,FILE *); 62 | extern int pagegen_days (Blog *,Request *,template__t const *,FILE *); 63 | extern int tumbler_page (Blog *,Request *,tumbler__s *,int (*)(Blog *,Request *,int,char const *,...)); 64 | extern void generic_cb (char const *,FILE *,void *); 65 | extern void generic_main (FILE *,struct callback_data *); 66 | extern bool run_hook (char const *,char const *[]); 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/blog.h: -------------------------------------------------------------------------------- 1 | /******************************************************************* 2 | * 3 | * Copyright 2001 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | ********************************************************************/ 22 | 23 | #ifndef I_A7907483_71BF_5594_9AA6_58C785CB9FFA 24 | #define I_A7907483_71BF_5594_9AA6_58C785CB9FFA 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include "timeutil.h" 33 | 34 | /*******************************************************************/ 35 | 36 | struct fields 37 | { 38 | size_t uid; 39 | size_t name; 40 | size_t email; 41 | }; 42 | 43 | struct author 44 | { 45 | char const *name; 46 | char const *email; 47 | char const *file; 48 | struct fields fields; 49 | }; 50 | 51 | struct bemail 52 | { 53 | char const *list; 54 | char const *message; 55 | char const *subject; 56 | bool notify; /* derived */ 57 | }; 58 | 59 | typedef struct template 60 | { 61 | char const *template; 62 | char const *file; 63 | char const *posthook; 64 | char const *pagegen; 65 | size_t items; 66 | bool reverse; 67 | bool fullurl; 68 | } template__t; 69 | 70 | typedef struct aflink 71 | { 72 | char const *proto; 73 | size_t psize; 74 | char const *format; 75 | } aflink__t; 76 | 77 | struct config 78 | { 79 | char const *name; 80 | char const *description; 81 | char const *class; 82 | char const *basedir; 83 | char const *lockfile; 84 | char const *webdir; 85 | char const *url; 86 | char const *prehook; 87 | char const *posthook; 88 | char const *adtag; 89 | char const *conversion; 90 | struct author author; 91 | template__t *templates; 92 | size_t templatenum; 93 | struct bemail email; 94 | aflink__t *affiliates; 95 | size_t affiliatenum; 96 | char const *baseurl; /* derived from URL */ 97 | lua_State *L; 98 | }; 99 | 100 | typedef struct blog 101 | { 102 | struct config config; 103 | struct btm first; 104 | struct btm last; 105 | struct btm now; 106 | time_t tnow; 107 | time_t lastmod; 108 | } Blog; 109 | 110 | typedef struct blogentry 111 | { 112 | Node node; 113 | bool valid; 114 | Blog *blog; 115 | time_t timestamp; 116 | struct btm when; 117 | char *title; 118 | char *class; 119 | char *author; 120 | char *status; 121 | char *adtag; 122 | char *body; 123 | } BlogEntry; 124 | 125 | /*********************************************************************/ 126 | 127 | extern Blog *BlogNew (char const *); 128 | extern void BlogFree (Blog *); 129 | extern BlogEntry *BlogEntryNew (Blog *); 130 | extern BlogEntry *BlogEntryRead (Blog *,struct btm const *); 131 | extern void BlogEntryReadBetweenU (Blog *,List *,struct btm const *restrict,struct btm const *restrict); 132 | extern void BlogEntryReadBetweenD (Blog *,List *,struct btm const *restrict,struct btm const *restrict); 133 | extern void BlogEntryReadXD (Blog *,List *,struct btm const *,size_t); 134 | extern void BlogEntryReadXU (Blog *,List *,struct btm const *,size_t); 135 | extern int BlogEntryWrite (BlogEntry *); 136 | extern size_t BlogLastEntry (Blog *,struct btm const *); 137 | extern int BlogEntryFree (BlogEntry *); 138 | 139 | /**********************************************************************/ 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /src/blogutil.c: -------------------------------------------------------------------------------- 1 | /**************************************************************** 2 | * 3 | * Copyright 2006 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *****************************************************************/ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "blogutil.h" 29 | 30 | /*******************************************************************/ 31 | 32 | String *tag_split(size_t *pnum,char const *tag) 33 | { 34 | size_t num = 0; 35 | size_t max = 0; 36 | String *pool = NULL; 37 | 38 | assert(pnum != NULL); 39 | assert(tag != NULL); 40 | 41 | while(*tag) 42 | { 43 | char const *p; 44 | 45 | if (num == max) 46 | { 47 | max += 1024; 48 | String *newpool = realloc(pool,max * sizeof(String)); 49 | if (newpool == NULL) 50 | break; 51 | pool = newpool; 52 | } 53 | 54 | for (p = tag ; (*p) && (*p != ',') ; p++) 55 | ; 56 | if (p == tag) break; 57 | 58 | pool[num].d = tag; 59 | pool[num++].s = p - tag; 60 | 61 | if (*p == '\0') 62 | break; 63 | for (p++ ; (*p) && isspace(*p) ; p++) 64 | ; 65 | tag = p; 66 | } 67 | 68 | *pnum = num; 69 | return pool; 70 | } 71 | 72 | /*********************************************************************/ 73 | 74 | char *fromstring(String const src) 75 | { 76 | assert(src.d != NULL); 77 | 78 | char *s = malloc(src.s + 1); 79 | if (s != NULL) 80 | { 81 | memcpy(s,src.d,src.s); 82 | s[src.s] = '\0'; 83 | } 84 | 85 | return s; 86 | } 87 | 88 | /*********************************************************************/ 89 | 90 | size_t fcopy(FILE *restrict out,FILE *restrict in) 91 | { 92 | char buffer[BUFSIZ]; 93 | size_t inbytes; 94 | size_t outbytes = 0; 95 | 96 | assert(out != NULL); 97 | assert(in != NULL); 98 | 99 | do 100 | { 101 | inbytes = fread(buffer,1,sizeof(buffer),in); 102 | outbytes += fwrite(buffer,1,inbytes,out); 103 | } while (inbytes > 0); 104 | 105 | return outbytes; 106 | } 107 | 108 | /*********************************************************************/ 109 | -------------------------------------------------------------------------------- /src/blogutil.h: -------------------------------------------------------------------------------- 1 | /**************************************************************** 2 | * 3 | * Copyright 2006 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *****************************************************************/ 22 | 23 | #ifndef I_F4A408FA_A630_59FB_81C6_846355DD0AFB 24 | #define I_F4A408FA_A630_59FB_81C6_846355DD0AFB 25 | 26 | #include 27 | 28 | /*********************************************************************/ 29 | 30 | typedef struct mystring 31 | { 32 | size_t s; 33 | char const *d; 34 | } String; 35 | 36 | /*********************************************************************/ 37 | 38 | extern String *tag_split (size_t *,char const *); 39 | extern char *fromstring (String const); 40 | extern size_t fcopy (FILE *restrict,FILE *restrict); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/conversion.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * 3 | * Copyright 2001 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *********************************************************************/ 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "conversion.h" 31 | #include "blogutil.h" 32 | 33 | /**********************************************************************/ 34 | 35 | struct nested_params 36 | { 37 | FILE *in; 38 | FILE *out; 39 | HtmlToken token; 40 | bool p; 41 | bool pre; 42 | bool list; 43 | bool blockquote; 44 | }; 45 | 46 | /**********************************************************************/ 47 | 48 | static void text_conversion_backend(FILE *restrict in,FILE *restrict out) 49 | { 50 | assert(in != NULL); 51 | assert(out != NULL); 52 | 53 | char *line = NULL; 54 | char *buffer = NULL; 55 | size_t bufsize = 0; 56 | size_t linesize = 0; 57 | FILE *tmpout = open_memstream(&buffer,&bufsize); 58 | 59 | while(!feof(in)) 60 | { 61 | ssize_t bytes; 62 | 63 | bytes = getline(&line,&linesize,in); 64 | if (bytes <= 0) break; 65 | line[--bytes] = '\0'; 66 | 67 | if (empty_string(line)) 68 | { 69 | fclose(tmpout); 70 | 71 | if (!empty_string(buffer)) 72 | fprintf(out,"

    %s

    \n",buffer); 73 | 74 | free(buffer); 75 | 76 | buffer = NULL; 77 | bufsize = 0; 78 | tmpout = open_memstream(&buffer,&bufsize); 79 | } 80 | else 81 | { 82 | FILE *tmpin; 83 | 84 | tmpin = fmemopen(line,bytes,"r"); 85 | buff_conversion(tmpin,tmpout); 86 | fclose(tmpin); 87 | fputc(' ',tmpout); 88 | } 89 | } 90 | 91 | fclose(tmpout); 92 | if (!empty_string(buffer)) 93 | fprintf(out,"

    %s

    \n",buffer); 94 | 95 | free(line); 96 | free(buffer); 97 | } 98 | 99 | /***********************************************************************/ 100 | 101 | static void entify_char(char *d,size_t ds,char *s,char e,char const *entity) 102 | { 103 | assert(d != NULL); 104 | assert(ds > 0); 105 | assert(s != NULL); 106 | assert(e != '\0'); 107 | assert(entity != NULL); 108 | 109 | size_t se = strlen(entity); 110 | 111 | for ( ; (*s) && (ds > 0) ; ) 112 | { 113 | if (*s == e) 114 | { 115 | if (ds < se) 116 | { 117 | *d = '\0'; 118 | return; 119 | } 120 | memcpy(d,entity,se); 121 | d += se; 122 | ds -= se; 123 | s++; 124 | } 125 | else 126 | { 127 | *d++ = *s++; 128 | ds--; 129 | } 130 | } 131 | 132 | *d = '\0'; 133 | } 134 | 135 | /**************************************************************************/ 136 | 137 | static void check_for_uri(struct nested_params *local,char const *attrib) 138 | { 139 | char newuri[BUFSIZ]; 140 | struct pair *src; 141 | struct pair *np; 142 | 143 | assert(local != NULL); 144 | assert(attrib != NULL); 145 | 146 | src = HtmlParseGetPair(local->token,attrib); 147 | if (src == NULL) return; 148 | 149 | entify_char(newuri,sizeof(newuri),src->value,'&',"&"); 150 | 151 | np = PairCreate(attrib,newuri); 152 | NodeInsert(&src->node,&np->node); 153 | NodeRemove(&src->node); 154 | PairFree(src); 155 | } 156 | 157 | /*************************************************************************/ 158 | 159 | static void html_handle_tag(struct nested_params *local) 160 | { 161 | assert(local != NULL); 162 | 163 | /*--------------------------------------------------------- 164 | ; tags that change a state 165 | ;----------------------------------------------------------*/ 166 | 167 | if (strcmp(HtmlParseValue(local->token),"P") == 0) 168 | local->p = true; 169 | else if (strcmp(HtmlParseValue(local->token),"/P") == 0) 170 | local->p = false; 171 | else if (strcmp(HtmlParseValue(local->token),"PRE") == 0) 172 | { 173 | local->pre = true; 174 | local->p = false; 175 | } 176 | else if (strcmp(HtmlParseValue(local->token),"/PRE") == 0) 177 | local->pre = false; 178 | else if 179 | ( 180 | (strcmp(HtmlParseValue(local->token),"TT") == 0) 181 | || (strcmp(HtmlParseValue(local->token),"CODE") == 0) 182 | || (strcmp(HtmlParseValue(local->token),"SAMP") == 0) 183 | || (strcmp(HtmlParseValue(local->token),"KBD") == 0) 184 | || (strcmp(HtmlParseValue(local->token),"VAR") == 0) 185 | ) 186 | local->pre = true; 187 | else if 188 | ( 189 | (strcmp(HtmlParseValue(local->token),"/VAR") == 0) 190 | || (strcmp(HtmlParseValue(local->token),"/KBD") == 0) 191 | || (strcmp(HtmlParseValue(local->token),"/SAMP") == 0) 192 | || (strcmp(HtmlParseValue(local->token),"/CODE") == 0) 193 | || (strcmp(HtmlParseValue(local->token),"/TT") == 0) 194 | ) 195 | local->pre = false; 196 | else if 197 | ( 198 | (strcmp(HtmlParseValue(local->token),"UL") == 0) 199 | || (strcmp(HtmlParseValue(local->token),"OL") == 0) 200 | ) 201 | local->list = true; 202 | else if 203 | ( 204 | (strcmp(HtmlParseValue(local->token),"/UL") == 0) 205 | || (strcmp(HtmlParseValue(local->token),"/OL") == 0) 206 | ) 207 | local->list = false; 208 | 209 | /*-------------------------------------------------------------- 210 | ; tags that have URIs that need to be checked for & and fixed. 211 | ;--------------------------------------------------------------*/ 212 | 213 | else if (strcmp(HtmlParseValue(local->token),"A") == 0) 214 | check_for_uri(local,"HREF"); 215 | else if (strcmp(HtmlParseValue(local->token),"BLOCKQUOTE") == 0) 216 | { 217 | local->p = false; 218 | local->blockquote = true; 219 | check_for_uri(local,"CITE"); 220 | } 221 | else if (strcmp(HtmlParseValue(local->token),"/BLOCKQUOTE") == 0) 222 | local->blockquote = false; 223 | else if (strcmp(HtmlParseValue(local->token),"AREA") == 0) 224 | check_for_uri(local,"HREF"); 225 | else if (strcmp(HtmlParseValue(local->token),"LINK") == 0) 226 | check_for_uri(local,"HREF"); 227 | else if (strcmp(HtmlParseValue(local->token),"BASE") == 0) 228 | check_for_uri(local,"HREF"); 229 | else if (strcmp(HtmlParseValue(local->token),"IMG") == 0) 230 | { 231 | check_for_uri(local,"SRC"); 232 | check_for_uri(local,"LONGDESC"); 233 | check_for_uri(local,"USEMAP"); 234 | } 235 | else if (strcmp(HtmlParseValue(local->token),"OBJECT") == 0) 236 | { 237 | check_for_uri(local,"CLASSID"); 238 | check_for_uri(local,"CODEBASE"); 239 | check_for_uri(local,"DATA"); 240 | check_for_uri(local,"ARCHIVE"); 241 | check_for_uri(local,"USEMAP"); 242 | } 243 | else if (strcmp(HtmlParseValue(local->token),"Q") == 0) 244 | check_for_uri(local,"CITE"); 245 | else if (strcmp(HtmlParseValue(local->token),"INS") == 0) 246 | check_for_uri(local,"CITE"); 247 | else if (strcmp(HtmlParseValue(local->token),"DEL") == 0) 248 | check_for_uri(local,"CITE"); 249 | else if (strcmp(HtmlParseValue(local->token),"FORM") == 0) 250 | check_for_uri(local,"ACTION"); 251 | else if (strcmp(HtmlParseValue(local->token),"INPUT") == 0) 252 | { 253 | check_for_uri(local,"SRC"); 254 | check_for_uri(local,"USEMAP"); 255 | } 256 | else if (strcmp(HtmlParseValue(local->token),"HEAD") == 0) 257 | check_for_uri(local,"PROFILE"); 258 | else if (strcmp(HtmlParseValue(local->token),"SCRIPT") == 0) 259 | { 260 | check_for_uri(local,"SRC"); 261 | check_for_uri(local,"FOR"); 262 | } 263 | else 264 | { 265 | if (!local->blockquote) 266 | local->p = false; 267 | } 268 | 269 | HtmlParsePrintTag(local->token,local->out); 270 | } 271 | 272 | /************************************************************************/ 273 | 274 | static void html_handle_string(struct nested_params *local) 275 | { 276 | assert(local != NULL); 277 | 278 | char *text = HtmlParseValue(local->token); 279 | 280 | if (!local->pre) 281 | { 282 | FILE *in = fmemopen(text,strlen(text),"r"); 283 | if (in != NULL) 284 | { 285 | buff_conversion(in,local->out); 286 | fclose(in); 287 | } 288 | } 289 | else 290 | fputs(text,local->out); 291 | } 292 | 293 | /************************************************************************/ 294 | 295 | static void html_handle_comment(struct nested_params *local) 296 | { 297 | assert(local != NULL); 298 | fprintf(local->out,"",HtmlParseValue(local->token)); 299 | } 300 | 301 | /**************************************************************************/ 302 | 303 | static void handle_backquote(FILE *restrict input,FILE *restrict output) 304 | { 305 | int c; 306 | 307 | assert(input != NULL); 308 | assert(output != NULL); 309 | 310 | if (feof(input)) 311 | { 312 | fputc('`',output); 313 | return; 314 | } 315 | 316 | c = fgetc(input); 317 | if (c == '`') 318 | fputs("“",output); 319 | else 320 | { 321 | fputc('`',output); 322 | ungetc(c,input); 323 | } 324 | } 325 | 326 | /********************************************************************/ 327 | 328 | static void handle_quote(FILE *restrict input,FILE *restrict output) 329 | { 330 | int c; 331 | 332 | assert(input != NULL); 333 | assert(output != NULL); 334 | 335 | if (feof(input)) 336 | { 337 | fputc('\'',output); 338 | return; 339 | } 340 | 341 | c = fgetc(input); 342 | if (c == '\'') 343 | fputs("”",output); 344 | else 345 | { 346 | fputc('\'',output); 347 | ungetc(c,input); 348 | } 349 | } 350 | 351 | /**********************************************************************/ 352 | 353 | static void handle_dash(FILE *restrict input,FILE *restrict output) 354 | { 355 | int c; 356 | 357 | assert(input != NULL); 358 | assert(output != NULL); 359 | 360 | if (feof(input)) 361 | { 362 | fputc('-',output); 363 | return; 364 | } 365 | 366 | c = fgetc(input); 367 | if (c == '-') 368 | { 369 | c = fgetc(input); 370 | if (c == '-') 371 | fputs("—",output); 372 | else 373 | { 374 | fputs("–",output); 375 | ungetc(c,input); 376 | } 377 | } 378 | else 379 | { 380 | fputc('-',output); 381 | ungetc(c,input); 382 | } 383 | } 384 | 385 | /********************************************************************/ 386 | 387 | static void handle_period(FILE *restrict input,FILE *restrict output) 388 | { 389 | int c; 390 | 391 | assert(input != NULL); 392 | assert(output != NULL); 393 | 394 | if (feof(input)) 395 | { 396 | fputc('.',output); 397 | return; 398 | } 399 | 400 | c = fgetc(input); 401 | if (c == '.') 402 | { 403 | c = fgetc(input); 404 | if (c == '.') 405 | fputs("…",output); 406 | else 407 | { 408 | fputs("‥",output); 409 | ungetc(c,input); 410 | } 411 | } 412 | else 413 | { 414 | fputc('.',output); 415 | ungetc(c,input); 416 | } 417 | } 418 | 419 | /*********************************************************************/ 420 | 421 | static ssize_t fj_write(void *cookie,char const *buffer,size_t bytes) 422 | { 423 | FILE *realout = cookie; 424 | size_t size = bytes; 425 | 426 | assert(cookie != NULL); 427 | assert(buffer != NULL); 428 | 429 | while(size) 430 | { 431 | switch(*buffer) 432 | { 433 | case '"' : fputc('\\',realout); fputc(*buffer,realout); break; 434 | case '\\': fputc('\\',realout); fputc(*buffer,realout); break; 435 | case '\b': fputc('\\',realout); fputc('b',realout); break; 436 | case '\f': fputc('\\',realout); fputc('f',realout); break; 437 | case '\n': fputc('\\',realout); fputc('n',realout); break; 438 | case '\r': fputc('\\',realout); fputc('r',realout); break; 439 | case '\t': fputc('\\',realout); fputc('t',realout); break; 440 | default: fputc(*buffer,realout); break; 441 | } 442 | buffer++; 443 | size--; 444 | } 445 | 446 | return bytes; 447 | } 448 | 449 | /*********************************************************************/ 450 | 451 | bool TO_email(char const *value,bool def) 452 | { 453 | if (!emptynull_string(value)) 454 | { 455 | if (strcmp(value,"no") == 0) 456 | return false; 457 | else if (strcmp(value,"yes") == 0) 458 | return true; 459 | } 460 | 461 | return def; 462 | } 463 | 464 | /**********************************************************************/ 465 | 466 | conversion__f TO_conversion(char const *name,char const *def) 467 | { 468 | assert(def != NULL); 469 | 470 | if (name == NULL) 471 | name = def; 472 | 473 | if (strcmp(name,"text") == 0) 474 | return text_conversion; 475 | else if (strcmp(name,"mixed") == 0) 476 | return mixed_conversion; 477 | else if (strcmp(name,"html") == 0) 478 | return html_conversion; 479 | else if (strcmp(name,"none") == 0) 480 | return no_conversion; 481 | else 482 | { 483 | syslog(LOG_WARNING,"conversion '%s' unsupported",name); 484 | return no_conversion; 485 | } 486 | } 487 | 488 | /**********************************************************************/ 489 | 490 | void no_conversion(FILE *restrict in,FILE *restrict out) 491 | { 492 | assert(in != NULL); 493 | assert(out != NULL); 494 | 495 | fcopy(out,in); 496 | } 497 | 498 | /**********************************************************************/ 499 | 500 | void text_conversion(FILE *restrict in,FILE *restrict out) 501 | { 502 | assert(in != NULL); 503 | assert(out != NULL); 504 | 505 | FILE *entin = fentity_encode_onread(in); 506 | text_conversion_backend(entin,out); 507 | fclose(entin); 508 | } 509 | 510 | /**********************************************************************/ 511 | 512 | void mixed_conversion(FILE *restrict in,FILE *restrict out) 513 | { 514 | FILE *tmpfp; 515 | char *text; 516 | size_t size; 517 | 518 | assert(in != NULL); 519 | assert(out != NULL); 520 | 521 | tmpfp = open_memstream(&text,&size); 522 | text_conversion_backend(in,tmpfp); 523 | fclose(tmpfp); 524 | 525 | tmpfp = fmemopen(text,size,"r"); 526 | html_conversion(tmpfp,out); 527 | fclose(tmpfp); 528 | 529 | free(text); 530 | } 531 | 532 | /*************************************************************************/ 533 | 534 | void html_conversion(FILE *restrict in,FILE *restrict out) 535 | { 536 | struct nested_params local; 537 | int t; 538 | 539 | assert(in != NULL); 540 | assert(out != NULL); 541 | 542 | local.in = in; 543 | local.out = out; 544 | local.p = false; 545 | local.pre = false; 546 | local.blockquote = false; 547 | local.list = false; 548 | 549 | local.token = HtmlParseNew(local.in); 550 | 551 | while((t = HtmlParseNext(local.token)) != T_EOF) 552 | { 553 | if (t == T_TAG) 554 | html_handle_tag(&local); 555 | else if (t == T_COMMENT) 556 | html_handle_comment(&local); 557 | else if (t == T_STRING) 558 | html_handle_string(&local); 559 | } 560 | 561 | HtmlParseFree(local.token); 562 | } 563 | 564 | /**********************************************************************/ 565 | 566 | void buff_conversion(FILE *restrict in,FILE *restrict out) 567 | { 568 | /*---------------------------------------------------- 569 | ; this is basically the macro substitution module. 570 | ;-----------------------------------------------------*/ 571 | 572 | assert(in != NULL); 573 | assert(out != NULL); 574 | 575 | /*------------------------------------------------------------------------ 576 | ; XXX how to handle using '"' for smart quotes, or regular quotes. I'd 577 | ; rather not have to do logic, but anything else might require more 578 | ; coupling than I want. Have to think on this. Also, add in some logic 579 | ; to handle '&' in strings. look for something like &[^\s]+; and if so, 580 | ; then pass it through unchanged; if not, then convert '&' to "&". 581 | ;------------------------------------------------------------------------*/ 582 | 583 | while(!feof(in)) 584 | { 585 | int c = fgetc(in); 586 | switch(c) 587 | { 588 | case EOF: break; 589 | case '`': handle_backquote(in,out); break; 590 | case '\'': handle_quote(in,out); break; 591 | case '-': handle_dash(in,out); break; 592 | case '.': handle_period(in,out); break; 593 | case '\0': assert(0); 594 | default: fputc(c,out); break; 595 | } 596 | } 597 | } 598 | 599 | /*****************************************************************/ 600 | 601 | FILE *fjson_encode_onwrite(FILE *out) 602 | { 603 | assert(out != NULL); 604 | return fopencookie(out,"w",(cookie_io_functions_t) { 605 | NULL, 606 | fj_write, 607 | NULL, 608 | NULL 609 | }); 610 | } 611 | 612 | /*********************************************************************/ 613 | -------------------------------------------------------------------------------- /src/conversion.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * 3 | * Copyright 2001 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *********************************************************************/ 22 | 23 | #ifndef I_18C80596_12AD_588C_8AC5_C30F6FDDBF24 24 | #define I_18C80596_12AD_588C_8AC5_C30F6FDDBF24 25 | 26 | #include 27 | #include 28 | #include "frontend.h" 29 | 30 | typedef void (*conversion__f)(FILE *restrict,FILE *restrict); 31 | 32 | extern bool TO_email (char const *,bool); 33 | extern conversion__f TO_conversion (char const *,char const *); 34 | extern void no_conversion (FILE *restrict,FILE *restrict); 35 | extern void text_conversion (FILE *restrict,FILE *restrict); 36 | extern void mixed_conversion (FILE *restrict,FILE *restrict); 37 | extern void html_conversion (FILE *restrict,FILE *restrict); 38 | extern void buff_conversion (FILE *restrict,FILE *restrict); 39 | extern FILE *fjson_encode_onwrite (FILE *); 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/frontend.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * 3 | * Copyright 2005 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *************************************************************************/ 22 | 23 | #ifndef I_CA412E0F_D512_5F3E_AB0B_4739574108FF 24 | #define I_CA412E0F_D512_5F3E_AB0B_4739574108FF 25 | 26 | #include 27 | 28 | #include "wbtum.h" 29 | #include "timeutil.h" 30 | #include "blog.h" 31 | 32 | typedef struct request 33 | { 34 | char *origauthor; 35 | char *author; 36 | char *title; 37 | char *class; 38 | char *status; 39 | char *date; 40 | char *adtag; 41 | char *origbody; 42 | char *body; 43 | char const *reqtumbler; 44 | struct btm when; 45 | tumbler__s tumbler; 46 | void (*conversion)(FILE *restrict,FILE *restrict); 47 | struct 48 | { 49 | unsigned int fullurl : 1; 50 | unsigned int reverse : 1; 51 | unsigned int navigation : 1; 52 | unsigned int navprev : 1; 53 | unsigned int navnext : 1; 54 | unsigned int edit : 1; 55 | unsigned int cgi : 1; 56 | unsigned int htmldump : 1; 57 | unsigned int email : 1; 58 | unsigned int emailin : 1; 59 | unsigned int regenerate : 1; 60 | unsigned int today : 1; 61 | unsigned int thisday : 1; 62 | } f; 63 | } Request; 64 | 65 | /************************************************/ 66 | 67 | extern char *get_remote_user (void); 68 | extern bool authenticate_author (Blog const *,Request *); 69 | extern void notify_emaillist (BlogEntry *); 70 | extern bool entry_add (Blog *,Request *); 71 | extern void fix_entry (Request *); 72 | extern char *entity_conversion (char const *); 73 | extern char *entity_encode (char const *); 74 | extern FILE *fentity_encode_onread (FILE *); 75 | extern FILE *fentity_encode_onwrite (FILE *); 76 | extern Request *request_init (Request *); 77 | extern void request_free (Request *); 78 | extern char *safe_strdup (char const *); 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /src/hooks.c: -------------------------------------------------------------------------------- 1 | /*********************************************** 2 | * 3 | * Copyright 2021 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | ************************************************/ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | /************************************************************************/ 38 | 39 | bool run_hook(char const *tag,char const *argv[]) 40 | { 41 | assert(tag != NULL); 42 | assert(argv != NULL); 43 | assert(argv[0] != NULL); 44 | 45 | pid_t child = fork(); 46 | 47 | if (child == -1) 48 | { 49 | syslog(LOG_ERR,"%s='%s' fork()='%s'",tag,argv[0],strerror(errno)); 50 | return false; 51 | } 52 | else if (child == 0) 53 | { 54 | extern char **environ; 55 | int devnull = open("/dev/null",O_RDWR); 56 | if (devnull == -1) 57 | _Exit(EX_UNAVAILABLE); 58 | if (dup2(devnull,STDIN_FILENO) == -1) 59 | _Exit(EX_OSERR); 60 | if (dup2(devnull,STDOUT_FILENO) == -1) 61 | _Exit(EX_OSERR); 62 | if (dup2(devnull,STDERR_FILENO) == -1) 63 | _Exit(EX_OSERR); 64 | for (int fh = STDERR_FILENO + 1 ; fh <= devnull ; fh++) 65 | if (close(fh) == -1) 66 | _Exit(EX_OSERR); 67 | execve((char *)argv[0],(char **)argv,environ); 68 | _Exit(EX_UNAVAILABLE); 69 | } 70 | else 71 | { 72 | int status; 73 | 74 | if (waitpid(child,&status,0) != child) 75 | { 76 | syslog(LOG_ERR,"%s='%s' waitpid()='%s'",tag,argv[0],strerror(errno)); 77 | return false; 78 | } 79 | 80 | if (WIFEXITED(status)) 81 | { 82 | if (WEXITSTATUS(status) != 0) 83 | { 84 | syslog(LOG_ERR,"%s='%s' status=%d",tag,argv[0],WEXITSTATUS(status)); 85 | return false; 86 | } 87 | } 88 | else 89 | { 90 | syslog(LOG_ERR,"%s='%s' terminated='%s'",tag,argv[0],strsignal(WTERMSIG(status))); 91 | return false; 92 | } 93 | } 94 | 95 | return true; 96 | } 97 | 98 | /************************************************************************/ 99 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * 3 | * Copyright 2005 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *************************************************************************/ 22 | 23 | #include 24 | #include "main.h" 25 | 26 | /************************************************************************/ 27 | 28 | int main(int argc,char *argv[]) 29 | { 30 | Cgi cgi; 31 | int rc; 32 | 33 | crashreport_args(argc,argv,true); 34 | crashreport_core(); 35 | 36 | cgi = CgiNew(); 37 | 38 | if (cgi != NULL) 39 | { 40 | switch(CgiMethod(cgi)) 41 | { 42 | case HEAD: 43 | case GET: rc = main_cgi_GET (cgi); break; 44 | case POST: rc = main_cgi_POST(cgi); break; 45 | case PUT: rc = main_cgi_PUT (cgi); break; 46 | default: rc = main_cgi_bad (cgi); break; 47 | } 48 | CgiFree(cgi); 49 | } 50 | else 51 | rc = main_cli(argc,argv); 52 | 53 | return rc; 54 | } 55 | 56 | /***********************************************************************/ 57 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | /************************************************************** 2 | * 3 | * Copyright 2001 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | ****************************************************************/ 22 | 23 | #ifndef I_82F50023_10E0_5FB8_A706_318F243A6012 24 | #define I_82F50023_10E0_5FB8_A706_318F243A6012 25 | 26 | #include 27 | #include 28 | 29 | #include "frontend.h" 30 | 31 | #define GENERATOR "mod_blog" " " PROG_VERSION 32 | 33 | extern int main_cgi_GET (Cgi); 34 | extern int main_cgi_POST (Cgi); 35 | extern int main_cgi_PUT (Cgi); 36 | extern int main_cgi_bad (Cgi); 37 | extern int main_cli (int,char *[]); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/main_cgi.c: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * 3 | * Copyright 2005 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *************************************************************************/ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include "backend.h" 31 | #include "frontend.h" 32 | #include "conversion.h" 33 | #include "main.h" 34 | 35 | typedef int (*cgicmd__f)(Cgi,Blog *,struct request *); 36 | 37 | /************************************************************************/ 38 | 39 | static int cgi_error(Blog *blog,Request *request,int level,char const *msg, ... ) 40 | { 41 | va_list args; 42 | char *file = NULL; 43 | char *errmsg = NULL; 44 | 45 | assert(level >= 400); 46 | assert(msg != NULL); 47 | 48 | va_start(args,msg); 49 | vasprintf(&errmsg,msg,args); 50 | va_end(args); 51 | 52 | if (level >= HTTP_ISERVERERR) 53 | syslog(LOG_ERR,"%d: %s",level,errmsg); 54 | 55 | asprintf(&file,"%s/errors/%d.html",getenv("DOCUMENT_ROOT"),level); 56 | 57 | if ((blog == NULL) || (freopen(file,"r",stdin) == NULL)) 58 | { 59 | char buf[BUFSIZ]; 60 | int len = snprintf( 61 | buf, 62 | sizeof(buf), 63 | "\n" 64 | "\n" 65 | "Error %d\n" 66 | "\n" 67 | "\n" 68 | "

    Error %d

    \n" 69 | "

    %s

    \n" 70 | "\n" 71 | "\n" 72 | "", 73 | level, 74 | level, 75 | errmsg 76 | ); 77 | printf( 78 | "Status: %d\r\n" 79 | "Content-Type: text/html\r\n" 80 | "Content-Length: %d\r\n" 81 | "\r\n" 82 | "%s" 83 | "", 84 | level, 85 | len, 86 | buf 87 | ); 88 | } 89 | else 90 | { 91 | struct callback_data cbd; 92 | 93 | assert(blog != NULL); 94 | assert(request != NULL); 95 | assert(request->f.cgi); 96 | 97 | request->f.htmldump = true; 98 | callback_init(&cbd,blog,request); 99 | cbd.status = level; 100 | generic_main(stdout,&cbd); 101 | } 102 | 103 | free(file); 104 | free(errmsg); 105 | 106 | return 0; 107 | } 108 | 109 | /**********************************************************************/ 110 | 111 | static void redirect(http__e status,char const *base,char const *path) 112 | { 113 | assert((status == HTTP_MOVEPERM) || (status == HTTP_MOVETEMP)); 114 | assert(base != NULL); 115 | assert(path != NULL); 116 | 117 | char doc[BUFSIZ]; 118 | int len = snprintf( 119 | doc, 120 | sizeof(doc), 121 | "\n" 122 | " Go here\n" 123 | " %s%s\n" 124 | "\n", 125 | base,path, 126 | base,path 127 | ); 128 | printf( 129 | "Status: %d\r\n" 130 | "Location: %s%s\r\n" 131 | "Content-Type: text/html\r\n" 132 | "Content-Length: %d\r\n" 133 | "\r\n" 134 | "%s", 135 | status, 136 | base, path, 137 | len, 138 | doc 139 | ); 140 | } 141 | 142 | /**********************************************************************/ 143 | 144 | static int cmd_cgi_error(Cgi cgi,Blog *blog,Request *request) 145 | { 146 | (void)cgi; 147 | return cgi_error(blog,request,HTTP_BADREQ,"Bad Request"); 148 | } 149 | 150 | /**********************************************************************/ 151 | 152 | static int cmd_cgi_get_new(Cgi cgi,Blog *blog,Request *req) 153 | { 154 | struct callback_data cbd; 155 | 156 | (void)cgi; 157 | (void)blog; 158 | assert(req != NULL); 159 | 160 | req->f.edit = true; 161 | generic_main(stdout,callback_init(&cbd,blog,req)); 162 | return 0; 163 | } 164 | 165 | /**********************************************************************/ 166 | 167 | static int cmd_cgi_get_show(Cgi cgi,Blog *blog,Request *req) 168 | { 169 | char *status; 170 | int rc = -1; 171 | 172 | assert(cgi != NULL); 173 | assert(blog != NULL); 174 | assert(req != NULL); 175 | 176 | status = CgiGetQValue(cgi,"status"); 177 | if (!emptynull_string(status)) 178 | { 179 | int level = strtoul(status,NULL,10); 180 | if (level >= 400) 181 | return cgi_error(blog,req,level,"Error from Apache"); 182 | } 183 | 184 | if ((emptynull_string(req->reqtumbler)) || (strcmp(req->reqtumbler,"/") == 0)) 185 | { 186 | redirect(HTTP_MOVEPERM,blog->config.url,""); 187 | rc = 0; 188 | } 189 | else 190 | { 191 | req->reqtumbler++; 192 | if (tumbler_new(&req->tumbler,req->reqtumbler,&blog->first,&blog->last)) 193 | { 194 | if (req->tumbler.redirect) 195 | { 196 | char *tum = tumbler_canonical(&req->tumbler); 197 | redirect(HTTP_MOVEPERM,blog->config.url,tum); 198 | free(tum); 199 | return 0; 200 | } 201 | 202 | rc = tumbler_page(blog,req,&req->tumbler,cgi_error); 203 | } 204 | else 205 | { 206 | char filename[FILENAME_MAX]; 207 | 208 | snprintf(filename,sizeof(filename),"%s%s",getenv("DOCUMENT_ROOT"),getenv("PATH_INFO")); 209 | if (freopen(filename,"r",stdin) == NULL) 210 | rc = cgi_error(blog,req,HTTP_NOTFOUND,"bad request"); 211 | else 212 | { 213 | struct callback_data cbd; 214 | 215 | req->f.htmldump = true; 216 | generic_main(stdout,callback_init(&cbd,blog,req)); 217 | rc = 0; 218 | } 219 | } 220 | } 221 | 222 | assert(rc != -1); 223 | return rc; 224 | } 225 | 226 | /********************************************************************/ 227 | 228 | static int cmd_cgi_get_today(Cgi cgi,Blog *blog,Request *req) 229 | { 230 | assert(cgi != NULL); 231 | assert(blog != NULL); 232 | assert(req != NULL); 233 | 234 | char *tpath = CgiGetQValue(cgi,"path"); 235 | char *twhen = CgiGetQValue(cgi,"day"); 236 | 237 | if ((tpath == NULL) && (twhen == NULL)) 238 | { 239 | return generate_thisday(blog,req,stdout,blog->now); 240 | } 241 | 242 | if (tpath == NULL) 243 | return cgi_error(blog,req,HTTP_BADREQ,"bad request"); 244 | 245 | if ((twhen == NULL) || (*twhen == '\0')) 246 | { 247 | redirect(HTTP_MOVEPERM,blog->config.url,tpath); 248 | return 0; 249 | } 250 | 251 | if (!thisday_new(&req->tumbler,twhen)) 252 | return cgi_error(blog,req,HTTP_BADREQ,"bad request"); 253 | 254 | if (req->tumbler.redirect) 255 | { 256 | char buf[BUFSIZ]; 257 | snprintf( 258 | buf, 259 | sizeof(buf), 260 | "%s/%02d/%02d", 261 | tpath,req->tumbler.start.month,req->tumbler.start.day 262 | ); 263 | redirect(HTTP_MOVEPERM,blog->config.url,buf); 264 | return 0; 265 | } 266 | 267 | return generate_thisday(blog,req,stdout,req->tumbler.start); 268 | } 269 | 270 | /**********************************************************************/ 271 | 272 | static int cmd_cgi_get_last(Cgi cgi,Blog *blog,Request *req) 273 | { 274 | char buf[BUFSIZ]; 275 | int len; 276 | 277 | assert(cgi != NULL); 278 | assert(blog != NULL); 279 | assert(req != NULL); 280 | 281 | char *date = CgiGetQValue(cgi,"date"); 282 | if (date == NULL) 283 | { 284 | len = snprintf( 285 | buf, 286 | sizeof(buf), 287 | "%s%04d/%02d/%02d.%d", 288 | blog->config.url, 289 | blog->last.year, 290 | blog->last.month, 291 | blog->last.day, 292 | blog->last.part 293 | ); 294 | } 295 | else 296 | { 297 | struct btm when; 298 | char *p; 299 | size_t last; 300 | 301 | when.year = strtoul(date,&p,10); p++; 302 | when.month = strtoul(p, &p,10); p++; 303 | when.day = strtoul(p, &p,10); 304 | when.part = 1; 305 | 306 | if (btm_cmp(&when,&blog->first) < 0) 307 | return cgi_error(blog,req,HTTP_BADREQ,"date out of range"); 308 | if (btm_cmp(&when,&blog->last) <= 0) 309 | last = BlogLastEntry(blog,&when); 310 | else 311 | last = 0; 312 | 313 | len = snprintf( 314 | buf, 315 | sizeof(buf), 316 | "%s%04d/%02d/%02d.%zu", 317 | blog->config.url, 318 | when.year, 319 | when.month, 320 | when.day, 321 | last 322 | ); 323 | } 324 | 325 | printf( 326 | "Status: %d\r\n" 327 | "Content-Type: text/plain\r\n" 328 | "Content-Length: %d\r\n" 329 | "\r\n" 330 | "%s", 331 | HTTP_OKAY, 332 | len, 333 | buf 334 | ); 335 | 336 | return 0; 337 | } 338 | 339 | /**********************************************************************/ 340 | 341 | static cgicmd__f set_m_cgi_get_command(char const *value) 342 | { 343 | if (emptynull_string(value)) 344 | return cmd_cgi_get_show; 345 | else if (strcmp(value,"new") == 0) 346 | return cmd_cgi_get_new; 347 | else if (strcmp(value,"show") == 0) 348 | return cmd_cgi_get_show; 349 | else if (strcmp(value,"preview") == 0) 350 | return cmd_cgi_get_show; 351 | else if (strcmp(value,"today") == 0) 352 | return cmd_cgi_get_today; 353 | else if (strcmp(value,"last") == 0) 354 | return cmd_cgi_get_last; 355 | else 356 | return cmd_cgi_error; 357 | } 358 | 359 | /***********************************************************************/ 360 | 361 | static void set_m_author(char *value,Request *req) 362 | { 363 | assert(req != NULL); 364 | 365 | if (emptynull_string(value)) 366 | req->author = get_remote_user(); 367 | else 368 | req->author = safe_strdup(value); 369 | 370 | req->origauthor = strdup(req->author); 371 | } 372 | 373 | /************************************************************************/ 374 | 375 | static int cmd_cgi_post_new(Cgi cgi,Blog *blog,Request *req) 376 | { 377 | (void)cgi; 378 | assert(blog != NULL); 379 | assert(req != NULL); 380 | 381 | if (entry_add(blog,req)) 382 | { 383 | generate_pages(blog,req); 384 | redirect(HTTP_MOVETEMP,blog->config.url,""); 385 | return 0; 386 | } 387 | else 388 | return cgi_error(blog,req,HTTP_ISERVERERR,"couldn't add entry"); 389 | } 390 | 391 | /***********************************************************************/ 392 | 393 | static int cmd_cgi_post_show(Cgi cgi,Blog *blog,Request *req) 394 | { 395 | struct callback_data cbd; 396 | BlogEntry *entry; 397 | 398 | (void)cgi; 399 | assert(blog != NULL); 400 | assert(req != NULL); 401 | 402 | /*----------------------------------------------------------------- 403 | ; this routine is a mixture between entry_add() and tumbler_page(). 404 | ; Perhaps some refactoring is in order at some point. 405 | ;------------------------------------------------------------------*/ 406 | 407 | callback_init(&cbd,blog,req); 408 | fix_entry(req); 409 | entry = BlogEntryNew(blog); 410 | 411 | if (emptynull_string(req->date)) 412 | entry->when = blog->now; 413 | else 414 | { 415 | char *p; 416 | 417 | entry->when.year = strtoul(req->date,&p,10); p++; 418 | entry->when.month = strtoul(p, &p,10); p++; 419 | entry->when.day = strtoul(p, &p,10); 420 | } 421 | 422 | entry->when.part = 1; /* doesn't matter what this is */ 423 | entry->timestamp = blog->tnow; 424 | entry->title = strdup(req->title); 425 | entry->class = strdup(req->class); 426 | entry->status = strdup(req->status); 427 | entry->author = strdup(req->author); 428 | entry->body = strdup(req->body); 429 | 430 | ListAddTail(&cbd.list,&entry->node); 431 | req->f.edit = true; 432 | generic_main(stdout,&cbd); 433 | 434 | return 0; 435 | } 436 | 437 | /**********************************************************************/ 438 | 439 | static cgicmd__f set_m_cgi_post_command(char const *value) 440 | { 441 | assert(value != NULL); 442 | 443 | if (emptynull_string(value)) 444 | return cmd_cgi_post_show; 445 | else if (strcmp(value,"new") == 0) 446 | return cmd_cgi_post_new; 447 | else if (strcmp(value,"show") == 0) 448 | return cmd_cgi_post_show; 449 | else 450 | return cmd_cgi_error; 451 | } 452 | 453 | /************************************************************************/ 454 | 455 | int main_cgi_bad(Cgi cgi) 456 | { 457 | (void)cgi; 458 | return cgi_error(NULL,NULL,HTTP_METHODNOTALLOWED,"Nope, not allowed."); 459 | } 460 | 461 | /*************************************************************************/ 462 | 463 | int main_cgi_GET(Cgi cgi) 464 | { 465 | assert(cgi != NULL); 466 | 467 | Request request; 468 | Blog *blog = BlogNew(NULL); 469 | 470 | if (blog == NULL) 471 | return cgi_error(NULL,NULL,HTTP_ISERVERERR,"Could not instantiate the blog"); 472 | 473 | request_init(&request); 474 | request.f.cgi = true; 475 | request.reqtumbler = getenv("PATH_INFO"); 476 | 477 | if (CgiStatus(cgi) != HTTP_OKAY) 478 | cgi_error(blog,&request,CgiStatus(cgi),"processing error"); 479 | else 480 | (*set_m_cgi_get_command(CgiGetQValue(cgi,"cmd")))(cgi,blog,&request); 481 | 482 | BlogFree(blog); 483 | request_free(&request); 484 | return 0; 485 | } 486 | 487 | /************************************************************************/ 488 | 489 | int main_cgi_POST(Cgi cgi) 490 | { 491 | assert(cgi != NULL); 492 | 493 | Request request; 494 | Blog *blog = BlogNew(NULL); 495 | 496 | if (blog == NULL) 497 | return cgi_error(NULL,NULL,HTTP_ISERVERERR,"Could not instantiate the blog"); 498 | 499 | request_init(&request); 500 | request.f.cgi = true; 501 | 502 | if (CgiStatus(cgi) != HTTP_OKAY) 503 | { 504 | cgi_error(blog,&request,CgiStatus(cgi),"processing error"); 505 | goto cleanup_return; 506 | } 507 | 508 | set_m_author(CgiGetValue(cgi,"author"),&request); 509 | 510 | request.title = safe_strdup(CgiGetValue(cgi,"title")); 511 | request.class = safe_strdup(CgiGetValue(cgi,"class")); 512 | request.status = safe_strdup(CgiGetValue(cgi,"status")); 513 | request.date = safe_strdup(CgiGetValue(cgi,"date")); 514 | request.adtag = safe_strdup(CgiGetValue(cgi,"adtag")); 515 | request.origbody = safe_strdup(CgiGetValue(cgi,"body")); 516 | request.body = safe_strdup(request.origbody); 517 | request.conversion = TO_conversion(CgiGetValue(cgi,"filter"),blog->config.conversion); 518 | request.f.email = TO_email(CgiGetValue(cgi,"email"),blog->config.email.notify); 519 | 520 | if ( 521 | (emptynull_string(request.author)) 522 | || (emptynull_string(request.title)) 523 | || (emptynull_string(request.body)) 524 | ) 525 | { 526 | cgi_error(blog,&request,HTTP_BADREQ,"errors-missing"); 527 | goto cleanup_return; 528 | } 529 | 530 | if (authenticate_author(blog,&request) == false) 531 | { 532 | syslog(LOG_ERR,"'%s' not authorized to post",request.author); 533 | cgi_error(blog,&request,HTTP_UNAUTHORIZED,"errors-author not authenticated got [%s] wanted [%s]",request.author,CgiGetValue(cgi,"author")); 534 | } 535 | else 536 | (*set_m_cgi_post_command(CgiGetValue(cgi,"cmd")))(cgi,blog,&request); 537 | 538 | cleanup_return: 539 | BlogFree(blog); 540 | request_free(&request); 541 | return 0; 542 | } 543 | 544 | /************************************************************************/ 545 | 546 | int main_cgi_PUT(Cgi cgi) 547 | { 548 | assert(cgi != NULL); 549 | 550 | if (CgiStatus(cgi) != HTTP_OKAY) 551 | return cgi_error(NULL,NULL,CgiStatus(cgi),"processing error"); 552 | 553 | Blog *blog = BlogNew(NULL); 554 | 555 | if (blog == NULL) 556 | return cgi_error(NULL,NULL,HTTP_ISERVERERR,"Could not instantiate the blog"); 557 | 558 | if (getenv("HTTP_BLOG_FILE") == NULL) 559 | { 560 | Request request; 561 | 562 | request_init(&request); 563 | set_m_author(getenv("HTTP_BLOG_AUTHOR"),&request); 564 | 565 | request.title = safe_strdup(getenv("HTTP_BLOG_TITLE")); 566 | request.class = safe_strdup(getenv("HTTP_BLOG_CLASS")); 567 | request.status = safe_strdup(getenv("HTTP_BLOG_STATUS")); 568 | request.date = safe_strdup(getenv("HTTP_BLOG_DATE")); 569 | request.adtag = safe_strdup(getenv("HTTP_BLOG_ADTAG")); 570 | request.conversion = TO_conversion(getenv("HTTP_BLOG_FILTER"),blog->config.conversion); 571 | request.f.email = TO_email(getenv("HTTP_BLOG_EMAIL"),blog->config.email.notify); 572 | request.body = malloc(CgiContentLength(cgi) + 1); 573 | 574 | fread(request.body,1,CgiContentLength(cgi),stdin); 575 | request.body[CgiContentLength(cgi)] = '\0'; 576 | 577 | if ( 578 | (emptynull_string(request.author)) 579 | || (emptynull_string(request.title)) 580 | || (emptynull_string(request.body)) 581 | ) 582 | { 583 | cgi_error(NULL,NULL,HTTP_BADREQ,"errors-missing"); 584 | goto cleanup_return; 585 | } 586 | 587 | if (!authenticate_author(blog,&request)) 588 | { 589 | syslog(LOG_ERR,"'%s' not authorized to post",request.author); 590 | cgi_error(NULL,NULL,HTTP_UNAUTHORIZED,"errors-author not authenticatged got [%s] wanted [%s]",request.author,getenv("HTTP_BLOG_AUTHOR")); 591 | goto cleanup_return; 592 | } 593 | 594 | if (entry_add(blog,&request)) 595 | { 596 | generate_pages(blog,&request); 597 | printf("Status: %d\r\n\r\n",HTTP_NOCONTENT); 598 | } 599 | else 600 | cgi_error(NULL,NULL,HTTP_ISERVERERR,"couldn't add entry"); 601 | 602 | cleanup_return: 603 | request_free(&request); 604 | BlogFree(blog); 605 | return 0; 606 | } 607 | else 608 | { 609 | char filename[FILENAME_MAX]; 610 | size_t bytes; 611 | FILE *fp; 612 | char const *path = getenv("PATH_INFO"); 613 | 614 | if (path == NULL) 615 | { 616 | cgi_error(NULL,NULL,HTTP_ISERVERERR,"couldn't add file"); 617 | goto cleanup_file_return; 618 | } 619 | 620 | snprintf(filename,sizeof(filename),"%s%s",blog->config.basedir,path); 621 | fp = fopen(filename,"wb"); 622 | if (fp == NULL) 623 | { 624 | cgi_error(NULL,NULL,HTTP_ISERVERERR,"%s: %s",path,strerror(errno)); 625 | goto cleanup_file_return; 626 | } 627 | 628 | do 629 | { 630 | char buffer[BUFSIZ]; 631 | 632 | bytes = fread(buffer,1,sizeof(buffer),stdin); 633 | fwrite(buffer,1,bytes,fp); 634 | } while (bytes > 0); 635 | 636 | fclose(fp); 637 | printf("Status: %d\r\n\r\n",HTTP_NOCONTENT); 638 | cleanup_file_return: 639 | BlogFree(blog); 640 | return 0; 641 | } 642 | } 643 | 644 | /************************************************************************/ 645 | -------------------------------------------------------------------------------- /src/main_cli.c: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * 3 | * Copyright 2005 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *************************************************************************/ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #include "backend.h" 36 | #include "frontend.h" 37 | #include "blogutil.h" 38 | #include "conversion.h" 39 | #include "main.h" 40 | 41 | typedef int (*clicmd__f)(Blog *,Request *); 42 | 43 | /*******************************************************************/ 44 | 45 | static int mailfile_readdata(Blog *blog,Request *req) 46 | { 47 | FILE *output; 48 | List headers; 49 | size_t size; 50 | 51 | assert(blog != NULL); 52 | assert(req != NULL); 53 | 54 | ListInit(&headers); 55 | RFC822HeadersRead(stdin,&headers); 56 | 57 | req->author = safe_strdup(PairListGetValue(&headers,"AUTHOR")); 58 | req->title = safe_strdup(PairListGetValue(&headers,"TITLE")); 59 | req->class = safe_strdup(PairListGetValue(&headers,"CLASS")); 60 | req->status = safe_strdup(PairListGetValue(&headers,"STATUS")); 61 | req->date = safe_strdup(PairListGetValue(&headers,"DATE")); 62 | req->adtag = safe_strdup(PairListGetValue(&headers,"ADTAG")); 63 | req->conversion = TO_conversion(PairListGetValue(&headers,"FILTER"),blog->config.conversion); 64 | req->f.email = TO_email(PairListGetValue(&headers,"EMAIL"),blog->config.email.notify); 65 | 66 | PairListFree(&headers); /* got everything we need, dump this */ 67 | 68 | if (authenticate_author(blog,req) == false) 69 | { 70 | syslog(LOG_ERR,"'%s' not authorized to post",req->author); 71 | return EPERM; 72 | } 73 | 74 | output = open_memstream(&req->origbody,&size); 75 | fcopy(output,stdin); 76 | fclose(output); 77 | 78 | req->body = strdup(req->origbody); 79 | return 0; 80 | } 81 | 82 | /***************************************************************************/ 83 | 84 | static int mail_setup_data(Blog *blog,Request *req) 85 | { 86 | List headers; 87 | char *line = NULL; 88 | size_t size = 0; 89 | 90 | assert(blog != NULL); 91 | assert(req != NULL); 92 | 93 | ListInit(&headers); 94 | getline(&line,&size,stdin); /* skip Unix 'From ' line */ 95 | free(line); 96 | 97 | /*---------------------------------------------------------------------- 98 | ; In the past, entries have been sent that have been encoded as 99 | ; "quoted-printable". I'm not sure the exact conditions that cause it 100 | ; (recents tests with known non-7bit clean content have been passed 101 | ; through unencoded) but it is happening now that I've somewhat changed 102 | ; how I write entries with quoted material. 103 | ; 104 | ; For now, check for the existence of the Content-Transfer-Encoding header 105 | ; and if it's not '7bit', '8bit' or 'binary' (which are identity 106 | ; encodings) then stop further processing and signal an error. 107 | ; 108 | ; At some future point, we might want to decode encoded entries sent via 109 | ; email. 110 | ;-----------------------------------------------------------------------*/ 111 | 112 | RFC822HeadersRead(stdin,&headers); 113 | 114 | char *encoding = PairListGetValue(&headers,"CONTENT-TRANSFER-ENCODING"); 115 | if (encoding) 116 | { 117 | down_string(encoding); 118 | if ( 119 | (strcmp(encoding,"7bit") != 0) 120 | && (strcmp(encoding,"8bit") != 0) 121 | && (strcmp(encoding,"binary") != 0) 122 | ) 123 | { 124 | syslog(LOG_ERR,"Content-Transfer-Encoding of '%s' not allowed!",encoding); 125 | PairListFree(&headers); 126 | return EINVAL; 127 | } 128 | } 129 | 130 | PairListFree(&headers); 131 | 132 | return mailfile_readdata(blog,req); 133 | } 134 | 135 | /*******************************************************************/ 136 | 137 | static int cli_error(Blog *blog,Request *request,int level,char const *msg, ... ) 138 | { 139 | va_list args; 140 | 141 | (void)blog; 142 | (void)request; 143 | assert(level >= 0); 144 | assert(msg != NULL); 145 | 146 | fprintf(stderr,"Error %d: ",level); 147 | 148 | va_start(args,msg); 149 | vfprintf(stderr,msg,args); 150 | va_end(args); 151 | 152 | fputc('\n',stderr); 153 | 154 | return 1; 155 | } 156 | 157 | /*************************************************************************/ 158 | 159 | static int cmd_cli_error(Blog *blog,Request *req) 160 | { 161 | return cli_error(blog,req,HTTP_BADREQ,"Bad request"); 162 | } 163 | 164 | /*************************************************************************/ 165 | 166 | static int cmd_cli_new(Blog *blog,Request *req) 167 | { 168 | int rc; 169 | 170 | assert(blog != NULL); 171 | assert(req != NULL); 172 | 173 | if (req->f.emailin) 174 | rc = mail_setup_data(blog,req); 175 | else 176 | rc = mailfile_readdata(blog,req); 177 | 178 | if (rc != 0) 179 | { 180 | fprintf(stderr,"Cannot process new entry\n"); 181 | return EXIT_FAILURE; 182 | } 183 | 184 | if (entry_add(blog,req)) 185 | { 186 | generate_pages(blog,req); 187 | return 0; 188 | } 189 | else 190 | { 191 | fprintf(stderr,"Failed to create new entry\n"); 192 | return EXIT_FAILURE; 193 | } 194 | } 195 | 196 | /****************************************************************************/ 197 | 198 | static int cmd_cli_show(Blog *blog,Request *req) 199 | { 200 | int rc = -1; 201 | 202 | assert(blog != NULL); 203 | assert(req != NULL); 204 | 205 | if (req->f.regenerate) 206 | rc = generate_pages(blog,req); 207 | else if (req->f.today) 208 | rc = generate_thisday(blog,req,stdout,blog->now); 209 | else if (req->f.thisday) 210 | { 211 | if (!thisday_new(&req->tumbler,req->reqtumbler)) 212 | rc = cli_error(blog,req,HTTP_BADREQ,"bad request"); 213 | else if (req->tumbler.redirect) 214 | rc = cli_error(blog,req,HTTP_MOVEPERM,"Redirect: %02d/%02d",req->tumbler.start.month,req->tumbler.start.day); 215 | else 216 | rc = generate_thisday(blog,req,stdout,req->tumbler.start); 217 | } 218 | else 219 | { 220 | if (req->reqtumbler == NULL) 221 | { 222 | template__t template; 223 | 224 | template.template = blog->config.templates[0].template; 225 | template.items = blog->config.templates[0].items; 226 | template.pagegen = "days"; 227 | template.reverse = true; 228 | template.fullurl = false; 229 | 230 | rc = pagegen_days(blog,req,&template,stdout); 231 | } 232 | else 233 | { 234 | if (tumbler_new(&req->tumbler,req->reqtumbler,&blog->first,&blog->last)) 235 | { 236 | if (req->tumbler.redirect) 237 | { 238 | char *tum = tumbler_canonical(&req->tumbler); 239 | rc = cli_error(blog,req,HTTP_MOVEPERM,"Redirect: %s",tum); 240 | free(tum); 241 | return rc; 242 | } 243 | rc = tumbler_page(blog,req,&req->tumbler,cli_error); 244 | } 245 | else 246 | rc = cli_error(blog,req,HTTP_NOTFOUND,"tumbler error---nothing found"); 247 | } 248 | } 249 | 250 | assert(rc != -1); 251 | return rc; 252 | } 253 | 254 | /********************************************************************/ 255 | 256 | static clicmd__f get_cli_command(char const *value) 257 | { 258 | if (emptynull_string(value)) 259 | return cmd_cli_show; 260 | 261 | if (strcmp(value,"new") == 0) 262 | return cmd_cli_new; 263 | else if (strcmp(value,"show") == 0) 264 | return cmd_cli_show; 265 | else if (strcmp(value,"preview") == 0) 266 | return cmd_cli_show; 267 | else 268 | return cmd_cli_error; 269 | } 270 | 271 | /*************************************************************************/ 272 | 273 | int main_cli(int argc,char *argv[]) 274 | { 275 | enum 276 | { 277 | OPT_NONE, 278 | OPT_CONFIG, 279 | OPT_CMD, 280 | OPT_FILE, 281 | OPT_EMAIL, 282 | OPT_ENTRY, 283 | OPT_REGENERATE, 284 | OPT_FORCENOTIFY, 285 | OPT_TODAY, 286 | OPT_THISDAY, 287 | OPT_HELP, 288 | }; 289 | 290 | static struct option const coptions[] = 291 | { 292 | { "config" , required_argument , NULL , OPT_CONFIG } , 293 | { "regenerate" , no_argument , NULL , OPT_REGENERATE } , 294 | { "regen" , no_argument , NULL , OPT_REGENERATE } , 295 | { "cmd" , required_argument , NULL , OPT_CMD } , 296 | { "file" , required_argument , NULL , OPT_FILE } , 297 | { "email" , no_argument , NULL , OPT_EMAIL } , 298 | { "entry" , required_argument , NULL , OPT_ENTRY } , 299 | { "force-notify" , no_argument , NULL , OPT_FORCENOTIFY } , 300 | { "today" , no_argument , NULL , OPT_TODAY } , 301 | { "thisday" , required_argument , NULL , OPT_THISDAY } , 302 | { "help" , no_argument , NULL , OPT_HELP } , 303 | { NULL , 0 , NULL , 0 } 304 | }; 305 | 306 | char *config = NULL; 307 | bool forcenotify = false; 308 | clicmd__f command = cmd_cli_show; 309 | Blog *blog; 310 | Request request; 311 | int rc; 312 | 313 | request_init(&request); 314 | 315 | while(true) 316 | { 317 | int option = 0; 318 | int c; 319 | 320 | c = getopt_long_only(argc,argv,"",coptions,&option); 321 | if (c == EOF) 322 | break; 323 | else switch(c) 324 | { 325 | case OPT_CONFIG: 326 | config = optarg; 327 | break; 328 | case OPT_FILE: 329 | if (freopen(optarg,"r",stdin) == NULL) 330 | return cli_error(NULL,NULL,HTTP_ISERVERERR,"%s: %s",optarg,strerror(errno)); 331 | break; 332 | case OPT_EMAIL: 333 | request.f.emailin = true; 334 | break; 335 | case OPT_ENTRY: 336 | request.reqtumbler = optarg; 337 | break; 338 | case OPT_REGENERATE: 339 | request.f.regenerate = true; 340 | break; 341 | case OPT_FORCENOTIFY: 342 | forcenotify = true; 343 | break; 344 | case OPT_TODAY: 345 | request.f.today = true; 346 | break; 347 | case OPT_THISDAY: 348 | request.f.thisday = true; 349 | request.reqtumbler = optarg; 350 | break; 351 | case OPT_CMD: 352 | command = get_cli_command(optarg); 353 | break; 354 | case OPT_HELP: 355 | default: 356 | fprintf( 357 | stderr, 358 | "usage: %s --options... \n" 359 | "\t--config file\n" 360 | "\t--regenerate | --regen\n" 361 | "\t--cmd ('new' | 'show' * | 'preview')\n" 362 | "\t--file file\n" 363 | "\t--email\n" 364 | "\t--entry \n" 365 | "\t--force-notify\n" 366 | "\t--today\n" 367 | "\t--thisday /\n" 368 | "\t--help\n" 369 | "\n" 370 | "\tVersion: " GENERATOR "\n" 371 | "\t\t%s\n" 372 | "\t\t%s\n" 373 | "\t\t%s\n" 374 | "\t* default value\n" 375 | "", 376 | argv[0], 377 | cgilib_version, 378 | LUA_RELEASE, 379 | gdbm_version 380 | ); 381 | return EXIT_FAILURE; 382 | } 383 | } 384 | 385 | blog = BlogNew(config); 386 | if (blog == NULL) 387 | return cli_error(NULL,NULL,HTTP_ISERVERERR,"%s: failed to initialize",config ? config : "missing config file"); 388 | 389 | if (forcenotify) 390 | { 391 | if (!blog->config.email.notify) 392 | { 393 | fprintf(stderr,"No email notifiation list\n"); 394 | rc = EXIT_FAILURE; 395 | goto cleanup_return; 396 | } 397 | 398 | BlogEntry *entry = BlogEntryRead(blog,&blog->last); 399 | 400 | if (entry != NULL) 401 | { 402 | notify_emaillist(entry); 403 | BlogEntryFree(entry); 404 | rc = EXIT_SUCCESS; 405 | goto cleanup_return; 406 | } 407 | else 408 | { 409 | fprintf(stderr,"Cannot force notify\n"); 410 | rc = EXIT_FAILURE; 411 | goto cleanup_return; 412 | } 413 | } 414 | 415 | rc = (*command)(blog,&request); 416 | cleanup_return: 417 | BlogFree(blog); 418 | request_free(&request); 419 | return rc; 420 | } 421 | 422 | /************************************************************************/ 423 | -------------------------------------------------------------------------------- /src/misc.c: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * 3 | * Copyright 2024 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | **********************************************************************/ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "frontend.h" 28 | #include "conversion.h" 29 | 30 | /************************************************************************/ 31 | 32 | char *safe_strdup(char const *orig) 33 | { 34 | return orig != NULL ? strdup(orig) : strdup(""); 35 | } 36 | 37 | /************************************************************************/ 38 | 39 | Request *request_init(Request *request) 40 | { 41 | assert(request != NULL); 42 | 43 | memset(request,0,sizeof(Request)); 44 | request->origauthor = NULL; 45 | request->author = NULL; 46 | request->title = NULL; 47 | request->class = NULL; 48 | request->status = NULL; 49 | request->date = NULL; 50 | request->adtag = NULL; 51 | request->origbody = NULL; 52 | request->body = NULL; 53 | request->reqtumbler = NULL; 54 | request->conversion = no_conversion; 55 | return request; 56 | } 57 | 58 | /************************************************************************/ 59 | 60 | void request_free(Request *request) 61 | { 62 | assert(request != NULL); 63 | 64 | free(request->origauthor); 65 | free(request->author); 66 | free(request->title); 67 | free(request->class); 68 | free(request->status); 69 | free(request->date); 70 | free(request->adtag); 71 | free(request->origbody); 72 | free(request->body); 73 | } 74 | 75 | /************************************************************************/ 76 | -------------------------------------------------------------------------------- /src/timeutil.c: -------------------------------------------------------------------------------- 1 | /************ 2 | * 3 | * Copyright 2001-2007 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | ***************************/ 22 | 23 | #include 24 | 25 | #include "wbtum.h" 26 | 27 | /*************************************************************************/ 28 | 29 | int max_monthday(int year,int month) 30 | { 31 | static int const days[] = { 31,0,31,30,31,30,31,31,30,31,30,31 } ; 32 | 33 | assert(year > 1969); 34 | assert(month > 0); 35 | assert(month < 13); 36 | 37 | if (month == 2) 38 | { 39 | /*---------------------------------------------------------------------- 40 | ; in case you didn't know, leap years are those years that are divisible 41 | ; by 4, except if it's divisible by 100, then it's not, unless it's 42 | ; divisible by 400, then it is. 1800 and 1900 were NOT leap years, but 43 | ; 2000 is. 44 | ;----------------------------------------------------------------------*/ 45 | 46 | if ((year % 400) == 0) return 29; 47 | if ((year % 100) == 0) return 28; 48 | if ((year % 4) == 0) return 29; 49 | return 28; 50 | } 51 | else 52 | return days[month - 1]; 53 | } 54 | 55 | /***************************************************************************/ 56 | 57 | void btm_inc_day(struct btm *d) 58 | { 59 | assert(d != NULL); 60 | 61 | d->day ++; 62 | if (d->day > max_monthday(d->year,d->month)) 63 | { 64 | d->day = 1; 65 | btm_inc_month(d); 66 | } 67 | } 68 | 69 | /*************************************************************************/ 70 | 71 | void btm_dec_part(struct btm *d) 72 | { 73 | assert(d != NULL); 74 | 75 | d->part--; 76 | if (d->part == 0) 77 | { 78 | d->part = ENTRY_MAX; 79 | btm_dec_day(d); 80 | } 81 | } 82 | 83 | /************************************************************************/ 84 | 85 | void btm_dec_day(struct btm *d) 86 | { 87 | assert(d != NULL); 88 | 89 | d->day--; 90 | if (d->day == 0) 91 | { 92 | btm_dec_month(d); 93 | d->day = max_monthday(d->year,d->month); 94 | } 95 | } 96 | 97 | /***********************************************************************/ 98 | 99 | void btm_inc_month(struct btm *d) 100 | { 101 | assert(d != NULL); 102 | 103 | d->month++; 104 | if (d->month == 13) 105 | { 106 | d->month = 1; 107 | d->year++; 108 | } 109 | } 110 | 111 | /***********************************************************************/ 112 | 113 | void btm_dec_month(struct btm *d) 114 | { 115 | assert(d != NULL); 116 | 117 | d->month--; 118 | if (d->month == 0) 119 | { 120 | d->month = 12; 121 | d->year--; 122 | } 123 | } 124 | 125 | /***********************************************************************/ 126 | 127 | int btm_cmp( 128 | struct btm const *restrict d1, 129 | struct btm const *restrict d2 130 | ) 131 | { 132 | int rc; 133 | 134 | assert(d1 != NULL); 135 | assert(d2 != NULL); 136 | 137 | if ((rc = d1->year - d2->year)) return rc; 138 | if ((rc = d1->month - d2->month)) return rc; 139 | if ((rc = d1->day - d2->day)) return rc; 140 | if ((rc = d1->part - d2->part)) return rc; 141 | 142 | return 0; 143 | } 144 | 145 | /***********************************************************************/ 146 | 147 | int btm_cmp_date( 148 | struct btm const *restrict d1, 149 | struct btm const *restrict d2 150 | ) 151 | { 152 | int rc; 153 | 154 | assert(d1 != NULL); 155 | assert(d2 != NULL); 156 | 157 | if ((rc = d1->year - d2->year)) return rc; 158 | if ((rc = d1->month - d2->month)) return rc; 159 | if ((rc = d1->day - d2->day)) return rc; 160 | 161 | return 0; 162 | } 163 | 164 | /***************************************************************************/ 165 | 166 | int btm_cmp_month( 167 | struct btm const *restrict d1, 168 | struct btm const *restrict d2 169 | ) 170 | { 171 | int rc; 172 | 173 | assert(d1 != NULL); 174 | assert(d2 != NULL); 175 | 176 | if ((rc = d1->year - d2->year)) return rc; 177 | if ((rc = d1->month - d2->month)) return rc; 178 | 179 | return 0; 180 | } 181 | 182 | /***************************************************************************/ 183 | 184 | int btm_cmp_year( 185 | struct btm const *restrict d1, 186 | struct btm const *restrict d2 187 | ) 188 | { 189 | assert(d1 != NULL); 190 | assert(d2 != NULL); 191 | 192 | return d1->year - d2->year; 193 | } 194 | 195 | /***************************************************************************/ 196 | -------------------------------------------------------------------------------- /src/timeutil.h: -------------------------------------------------------------------------------- 1 | /******************************************** 2 | * 3 | * Copyright 2001 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | *********************************************/ 22 | 23 | #ifndef I_F3FFF9B1_0536_542C_B5AB_DB26462A0322 24 | #define I_F3FFF9B1_0536_542C_B5AB_DB26462A0322 25 | 26 | struct btm 27 | { 28 | int year; 29 | int month; 30 | int day; 31 | int part; 32 | }; 33 | 34 | /***********************************************************/ 35 | 36 | extern int max_monthday (int,int); 37 | extern void btm_inc_day (struct btm *); 38 | extern void btm_dec_part (struct btm *); 39 | extern void btm_dec_day (struct btm *); 40 | extern void btm_dec_month (struct btm *); 41 | extern void btm_inc_month (struct btm *); 42 | extern int btm_cmp (struct btm const *restrict,struct btm const *restrict); 43 | extern int btm_cmp_date (struct btm const *restrict,struct btm const *restrict); 44 | extern int btm_cmp_month (struct btm const *restrict,struct btm const *restrict); 45 | extern int btm_cmp_year (struct btm const *restrict,struct btm const *restrict); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/wbtum.h: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | * 3 | * Copyright 2015 by Sean Conner. All Rights Reserved. 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License 7 | * as published by the Free Software Foundation; either version 2 8 | * of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 | * 19 | * Comments, questions and criticisms can be sent to: sean@conman.org 20 | * 21 | **********************************************************************/ 22 | 23 | #ifndef I_92A09CC2_0922_5AE9_8A0E_3E71ED370C86 24 | #define I_92A09CC2_0922_5AE9_8A0E_3E71ED370C86 25 | 26 | #include 27 | #include 28 | #include "timeutil.h" 29 | 30 | /**********************************************************************/ 31 | 32 | #define ENTRY_MAX 23 33 | 34 | typedef enum unit__e 35 | { 36 | UNIT_YEAR, 37 | UNIT_MONTH, 38 | UNIT_DAY, 39 | UNIT_PART, 40 | } unit__e; 41 | 42 | typedef struct tumbler__s 43 | { 44 | struct btm start; /* starting date */ 45 | struct btm stop; /* ending date */ 46 | unit__e ustart; /* ending segment of initial part */ 47 | unit__e ustop; /* ending segment of range part */ 48 | int segments; /* used for range part */ 49 | bool file; /* a file was specified */ 50 | bool redirect; /* we need to redirect */ 51 | bool range; /* a range was specified */ 52 | char filename[FILENAME_MAX]; 53 | } tumbler__s; 54 | 55 | /***********************************************************************/ 56 | 57 | extern bool tumbler_new (tumbler__s *,char const *,struct btm const *restrict ,struct btm const *restrict); 58 | extern char *tumbler_canonical (tumbler__s const *); 59 | extern bool thisday_new (tumbler__s *,char const *); 60 | 61 | #endif 62 | --------------------------------------------------------------------------------