├── LICENSE ├── README.md ├── adminip ├── blockip ├── chat ├── chatlog.html ├── feed.html ├── form.html ├── ghostchat.go └── index.html ├── ghostchat.go ├── key ├── motd └── swearfilter /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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) 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 along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ghostchat 2 | 3 | Ghostchat v1.0 4 | 5 | Description: Simple anonymous HTTP chat server, requires no client side scripts. Works well with users on ancient machines. 6 | 7 | Author: Wiby Search Engine 8 | 9 | License: GPL v2 10 | 11 | Full source can be retrieved from http://wiby.me/download/ghostchat.zip or https://github.com/wibyweb/ghostchat/ 12 | 13 | 14 | Enter the chat: http://serverIP:PORT/chat/ - Hit refresh for it to begin working. Default port is 4444. 15 | 16 | 17 | -------------------------------------------------- 18 | Admin Commands (Place admin IPs inside 'adminip' file) 19 | -------------------------------------------------- 20 | 21 | Close chat: /close 22 | 23 | Open chat: /open 24 | 25 | Ban user and delete their posts: /ban userID 26 | 27 | Ban users and delete posts containing string: /banstr string 28 | 29 | Delete posts containing string: /delstr string 30 | 31 | Enable or clear chat log: /log 32 | 33 | Disable and delete chat log: /nolog 34 | 35 | Clear chat feed: /clear 36 | 37 | Change message of the day: /motd message 38 | 39 | Remove message of the day: /motd 40 | 41 | Filter swearwords: Add swearwords to 'swearfilter' file. 42 | 43 | -------------------------------------------------- 44 | 45 | Note: 46 | 47 | Anything inside the chat folder is served publicly. 48 | 49 | If cursor does not appear on form after pressing send, press Tab. 50 | 51 | Set a unique 93-byte key in the file called 'key'. Three bytes are used per day to create a 52 | 53 | usually unique ID based on the last 3 numbers of each client IP, ignoring octets. 54 | 55 | Full logs containing client IPs are located inside 'adminlog'. 56 | 57 | USE THIS CHAT SERVER AT YOUR OWN RISK. 58 | -------------------------------------------------------------------------------- /adminip: -------------------------------------------------------------------------------- 1 | 127.0.0.1 2 | -------------------------------------------------------------------------------- /blockip: -------------------------------------------------------------------------------- 1 | 123.123.123.123 -------------------------------------------------------------------------------- /chat/chatlog.html: -------------------------------------------------------------------------------- 1 | Hello
2 | -------------------------------------------------------------------------------- /chat/feed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Server is running.
7 | 8 | -------------------------------------------------------------------------------- /chat/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 |     Ghostchat v1.0 for Netscape 2+   [log]  [src] 7 |
8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /chat/ghostchat.go: -------------------------------------------------------------------------------- 1 | //Ghostchat v1.0 2 | //Description: Simple anonymous HTTP chat server, requires no client side scripts. Works well with users on ancient machines. 3 | //Author: Wiby Search Engine 4 | //License: GPL v2 5 | 6 | //A few extra files are needed. Get full source at http://wiby.me/download/ghostchat.zip or https://github.com/wibyweb/ghostchat/ 7 | 8 | //Enter chat: http://serverIP:PORT/chat/ - Hit refresh for it to begin working. Default port is 4444. 9 | 10 | //------------------------------------------------------ 11 | //Admin Commands (Place admin IPs inside 'adminip' file) 12 | //------------------------------------------------------ 13 | //Close chat: /close 14 | //Open chat: /open 15 | //Ban user and delete their posts: /ban userID 16 | //Ban users and delete posts containing string: /banstr string 17 | //Delete posts containing string: /delstr string 18 | //Enable or clear chat log: /log 19 | //Disable and delete chat log: /nolog 20 | //Clear chat feed: /clear 21 | //Change message of the day: /motd message 22 | //Remove message of the day: /motd 23 | //Filter swearwords: Add swearwords to 'swearfilter' file. 24 | //------------------------------------------------------ 25 | 26 | //Note: Anything inside the chat folder is served publicly. 27 | // If cursor does not appear on form after pressing send, press Tab. 28 | // Set a unique 93-byte key in the file called 'key'. Three bytes are used per day to create a 29 | // usually unique ID based on the last 3 numbers of each client IP, ignoring octets. 30 | // Full logs containing client IPs are located inside 'adminlog'. 31 | // USE THIS CHAT SERVER AT YOUR OWN RISK. 32 | 33 | package main 34 | 35 | import ( 36 | "fmt" 37 | "log" 38 | "os" 39 | "net/http" 40 | "io/ioutil" 41 | "strings" 42 | "time" 43 | "strconv" 44 | // "encoding/hex" 45 | "math/big" 46 | ) 47 | 48 | var feedheader string = "\n\n\n\n\n" 49 | var feedfooter string = "\n" 50 | 51 | func main() { 52 | http.HandleFunc("/chat/post",handler) 53 | http.HandleFunc("/", handler) 54 | log.Fatal(http.ListenAndServe(":4444", nil)) 55 | // log.Fatal(http.ListenAndServe("localhost:4444", nil))//For use behind a reverse proxy, make sure to send X-Real-IP header. Disable above line to use this one, also enable line 59 and disable line 60. 56 | } 57 | 58 | func handler(w http.ResponseWriter, r *http.Request) { 59 | //ip := r.Header.Get("X-Real-IP") //For use behind a reverse proxy. Make sure your reverse proxy is configured so header X-Real-IP cannot be spoofed. Also see comments on line 55 60 | ip := r.RemoteAddr[0:strings.LastIndex(r.RemoteAddr,":")] 61 | 62 | if checkban(ip) == false{ 63 | switch r.Method { 64 | case "GET": 65 | http.Handle("/chat/", http.StripPrefix("/chat/", http.FileServer(http.Dir("chat/")))) 66 | 67 | case "POST": 68 | // Call ParseForm() to parse the raw query and update r.PostForm and r.Form. 69 | if err := r.ParseForm(); err != nil { 70 | fmt.Fprintf(w, "ParseForm() err: %v", err) 71 | return 72 | } 73 | 74 | formMessage := r.FormValue("message") 75 | 76 | //check if an admin command was made 77 | command := 0 78 | if formMessage != "" && formMessage[0] == '/'{ 79 | command = commandhandler(formMessage, ip) 80 | } 81 | 82 | maxlines := 13 //max number of lines in feed. (13 is default for classic mac screen) 83 | 84 | //load contents of motd (message of the day) 85 | motdstring := "" 86 | motd, err := ioutil.ReadFile("motd") 87 | if err != nil { 88 | motdstring = "" 89 | } 90 | motdstring = string(motd) 91 | 92 | //load entire contents of feed 93 | feed, err := ioutil.ReadFile("chat/feed.html") 94 | if err != nil { 95 | panic(err) 96 | } 97 | feedstring := string(feed) 98 | 99 | if motdstring != "close\n" && motdstring != "close" && r.FormValue("message") != "" && command == 0{ 100 | formMessageRune := []rune(formMessage) 101 | if len(formMessageRune) > 180{//trim posts greater than 180 runes 102 | formMessageRune = formMessageRune[0:179] 103 | } 104 | formMessage = string(formMessageRune) 105 | formMessage = strings.Replace(formMessage, "\n", "", -1)//user cant insert line feeds 106 | formMessage = swearfilter(formMessage) 107 | thetime := time.Now() 108 | //thetime.Format("2006-01-02 15:04:05") 109 | message := thetime.Format("15:04") 110 | message += " <" 111 | message += createID(ip,thetime.Format("02")) 112 | message += "> " 113 | message += formMessage 114 | message = strings.Replace(message, "<", "<", -1)//user cant insert html 115 | message = strings.Replace(message, ">", ">", -1) 116 | messagerune := []rune(message) 117 | 118 | //readjust lines in feed if motd present 119 | if motdstring != ""{ 120 | motdrune := []rune(motdstring) 121 | if len(motdrune) > 95{ 122 | maxlines -= 1 123 | } 124 | maxlines -= 1 125 | } 126 | 127 | //remove html header from feed 128 | feedstring = strings.Replace(feedstring, feedheader, "", -1) 129 | feedstring = strings.Replace(feedstring, feedfooter, "", -1) 130 | 131 | feedlinecount := 0 132 | messagelinecount := 0 133 | totalfeedlines := 0 134 | 135 | //posted message can take up to 2 lines 136 | if len(messagerune) > 90{ 137 | messagelinecount += 2 138 | }else{ 139 | messagelinecount ++ 140 | } 141 | lines := strings.Split(feedstring, "
\n") 142 | feedtrimmed := "" 143 | rangecount := 1 144 | //find out how many lines there are currently in the feed 145 | for _, line := range lines{ 146 | //max 90 chars per line, and max message size is 180 or 2 lines 147 | linerune := []rune(line) 148 | if len(linerune) > 95{ 149 | totalfeedlines ++ 150 | } 151 | totalfeedlines ++ 152 | } 153 | //trim old lines that wont fit once message is appended 154 | for _, line := range lines{ 155 | //max 90 chars per line, and max message size is 180 or 2 lines 156 | linerune := []rune(line) 157 | if len(linerune) > 95{ 158 | feedlinecount ++ 159 | } 160 | feedlinecount ++ //chat message will not exceed 180chars or 2 lines max 161 | 162 | if(feedlinecount > messagelinecount){ 163 | if totalfeedlines - feedlinecount < maxlines { 164 | feedtrimmed += line 165 | if (len(lines) != rangecount){ 166 | feedtrimmed += "
\n" 167 | } 168 | } 169 | } 170 | rangecount ++ 171 | } 172 | //update the feed 173 | updatefeed := "" 174 | if (feedlinecount + messagelinecount > (maxlines+1)){ 175 | feedtrimmed += message 176 | updatefeed += feedtrimmed 177 | }else{ 178 | if motdstring != "" && feedstring != ""{//have to remove old motd line before appending it again 179 | motdend := strings.Index(feedstring,"
\n") 180 | feedstring = feedstring[motdend+5:len(feedstring)] 181 | } 182 | updatefeed += feedstring 183 | updatefeed += message 184 | } 185 | updatefeed += "
\n" 186 | updatedfeed := feedheader 187 | //if motd exists, append to feed. Make sure your motd isn't longer than 180 characters. 188 | if motdstring != "" { 189 | updatedfeed += motdstring 190 | updatedfeed += "
\n" 191 | } 192 | updatedfeed += updatefeed 193 | updatedfeed += feedfooter 194 | 195 | //write feed 196 | f, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 197 | if err != nil { 198 | panic(err) 199 | } 200 | defer f.Close() 201 | if _, err = f.WriteString(updatedfeed); err != nil { 202 | panic(err) 203 | } 204 | f.Sync() 205 | f.Close() 206 | 207 | //append log 208 | writelog(ip,message) 209 | 210 | //serve chat form again 211 | http.ServeFile(w, r, "chat/form.html") 212 | }else if motdstring == "close\n" || motdstring == "close"{ 213 | fmt.Fprintf(w, "The chat is currently closed.") 214 | }else{ 215 | //serve chat form again 216 | http.ServeFile(w, r, "chat/form.html") 217 | } 218 | 219 | default: 220 | fmt.Fprintf(w, "Only GET and POST is supported.") 221 | } 222 | }else{//Note that GET requests will still continue working until server is restarted or user restarts 223 | fmt.Fprintf(w, "403 Forbidden") 224 | } 225 | } 226 | 227 | func swearfilter(s string) string{ 228 | swearwords := "" 229 | swearfilter, err := ioutil.ReadFile("swearfilter") 230 | if err != nil { 231 | swearwords = "" 232 | }else{ 233 | swearwords = string(swearfilter) 234 | } 235 | swearwords = strings.Replace(swearwords, "\r", "", -1)//some editors like to insert carriage returns with line feeds 236 | if(len(swearwords) > 0){ 237 | if swearwords[len(swearwords)-1] == byte('\n'){//some text editors like to do this at the final byte, can't have that 238 | swearwords = swearwords[0:len(swearwords)-1] 239 | } 240 | swears := strings.Split(swearwords, "\n") 241 | sLower := strings.ToLower(s) 242 | for _, swearword := range swears{ 243 | if strings.Contains(sLower,swearword){ 244 | return "I have a potty mouth." 245 | } 246 | } 247 | } 248 | return s 249 | } 250 | 251 | func checkban(ip string) bool{ 252 | //fmt.Printf("\n%s",ip) 253 | //load entire contents of blockip 254 | blockip, err := ioutil.ReadFile("blockip") 255 | if err != nil { 256 | panic(err) 257 | } 258 | blockipstring := string(blockip) 259 | blockipstring = strings.Replace(blockipstring, "\r", "", -1) 260 | lines := strings.Split(blockipstring, "\n") 261 | for _, line := range lines{ 262 | if ip == line{ 263 | return true 264 | } 265 | } 266 | return false 267 | } 268 | 269 | func checkAdminIP(ip string) bool{ 270 | adminip, err := ioutil.ReadFile("adminip") 271 | if err != nil { 272 | panic(err) 273 | } 274 | adminipstring := string(adminip) 275 | adminipstring = strings.Replace(adminipstring, "\r", "", -1) 276 | lines := strings.Split(adminipstring, "\n") 277 | for _, line := range lines{ 278 | if ip == line{ 279 | return true 280 | } 281 | } 282 | return false 283 | } 284 | 285 | func writelog(ip string, message string){ 286 | logmessage := ip 287 | logmessage += " " 288 | logmessage += message 289 | logmessage += "\n" 290 | flog, err := os.OpenFile("adminlog", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) 291 | if err != nil { 292 | panic(err) 293 | } 294 | defer flog.Close() 295 | if _, err = flog.WriteString(logmessage); err != nil { 296 | panic(err) 297 | } 298 | flog.Sync() 299 | flog.Close() 300 | 301 | //appends to a public chat log (chatlog.html) but only if you create this file yourself 302 | fchatlog, err := os.OpenFile("chat/chatlog.html", os.O_APPEND|os.O_WRONLY, 0600) 303 | if err == nil { 304 | message += "
\n" 305 | defer fchatlog.Close() 306 | if _, err = fchatlog.WriteString(message); err != nil { 307 | panic(err) 308 | } 309 | fchatlog.Sync() 310 | } 311 | fchatlog.Close() 312 | } 313 | 314 | func commandhandler(formMessage string, ip string) int{ 315 | noun := "" 316 | if len(formMessage) > 6 && formMessage[0:6] == "/motd "{ 317 | noun = strings.TrimPrefix(formMessage, "/motd ") 318 | formMessage = "/motd" 319 | } 320 | if len(formMessage) > 5 && formMessage[0:5] == "/ban "{ 321 | noun = strings.TrimPrefix(formMessage, "/ban ") 322 | formMessage = "/ban" 323 | } 324 | if len(formMessage) > 8 && formMessage[0:8] == "/banstr "{ 325 | noun = strings.TrimPrefix(formMessage, "/banstr ") 326 | formMessage = "/banstr" 327 | } 328 | if len(formMessage) > 8 && formMessage[0:8] == "/delstr "{ 329 | noun = strings.TrimPrefix(formMessage, "/delstr ") 330 | formMessage = "/delstr" 331 | } 332 | switch formMessage{ //process any verbs 333 | case "/close": //close the chat 334 | if checkAdminIP(ip) == true{ 335 | motdstring := "" 336 | //read motd 337 | motd, err := ioutil.ReadFile("motd") 338 | if err == nil{ 339 | motdstring = string(motd) 340 | } 341 | if motdstring != "close"{ 342 | //backup motd 343 | motdbak, err := os.OpenFile("motdbak", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 344 | if err != nil { 345 | panic(err) 346 | } 347 | defer motdbak.Close() 348 | if _, err = motdbak.WriteString(motdstring); err != nil { 349 | panic(err) 350 | } 351 | motdbak.Sync() 352 | motdbak.Close() 353 | 354 | //write "close" to motd 355 | motdupdate, err := os.OpenFile("motd", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 356 | if err != nil { 357 | panic(err) 358 | } 359 | defer motdupdate.Close() 360 | if _, err = motdupdate.WriteString("close"); err != nil { 361 | panic(err) 362 | } 363 | motdupdate.Sync() 364 | motdupdate.Close() 365 | 366 | //update feed to stop the auto refresh 367 | feed, err := ioutil.ReadFile("chat/feed.html") 368 | if err != nil { 369 | panic(err) 370 | } 371 | feedstring := string(feed) 372 | feedstring = strings.Replace(feedstring, "http-equiv=\"refresh\" content=", "", -1) 373 | f, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 374 | if err != nil { 375 | panic(err) 376 | } 377 | defer f.Close() 378 | if _, err = f.WriteString(feedstring); err != nil { 379 | panic(err) 380 | } 381 | f.Sync() 382 | f.Close() 383 | } 384 | } 385 | return 0 386 | case "/open": //open the chat 387 | if checkAdminIP(ip) == true{ 388 | motdstring := "" 389 | //read motd 390 | motd, err := ioutil.ReadFile("motd") 391 | if err == nil{ 392 | motdstring = string(motd) 393 | } 394 | if motdstring == "close"{ 395 | //read motdbak 396 | motdbak, err := ioutil.ReadFile("motdbak") 397 | if err != nil { 398 | motdstring = "" 399 | }else{ 400 | motdstring = string(motdbak) 401 | } 402 | 403 | //restore motd 404 | motdrestore, err := os.OpenFile("motd", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 405 | if err != nil { 406 | panic(err) 407 | } 408 | defer motdrestore.Close() 409 | if _, err = motdrestore.WriteString(motdstring); err != nil { 410 | panic(err) 411 | } 412 | motdrestore.Sync() 413 | motdrestore.Close() 414 | } 415 | } 416 | return 0 417 | case "/log": //enable or clear chatlog 418 | if checkAdminIP(ip) == true{ 419 | chatlog, err := os.OpenFile("chat/chatlog.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 420 | if err != nil { 421 | panic(err) 422 | } 423 | chatlog.Close() 424 | } 425 | return 1 426 | case "/nolog": //delete and disable chatlog 427 | if checkAdminIP(ip) == true{ 428 | err := os.Remove("chat/chatlog.html") 429 | if err != nil { 430 | fmt.Printf("\nChatlog already disabled.") 431 | } 432 | } 433 | return 1 434 | case "/clear": //clear the feed 435 | if checkAdminIP(ip) == true{ 436 | feed, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 437 | if err != nil { 438 | panic(err) 439 | } 440 | defer feed.Close() 441 | if _, err = feed.WriteString(feedheader + feedfooter); err != nil { 442 | panic(err) 443 | } 444 | feed.Sync() 445 | feed.Close() 446 | } 447 | return 1 448 | case "/motd": //update motd - not adding a message will remove motd 449 | if checkAdminIP(ip) == true{ 450 | motdupdate, err := os.OpenFile("motd", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 451 | if err != nil { 452 | panic(err) 453 | } 454 | defer motdupdate.Close() 455 | if _, err = motdupdate.WriteString(noun); err != nil { 456 | panic(err) 457 | } 458 | motdupdate.Sync() 459 | motdupdate.Close() 460 | } 461 | return 0 462 | case "/ban": //ban a user and remove their prior posts 463 | if checkAdminIP(ip) == true && noun != ""{ 464 | //read chat feed 465 | feedstring := "" 466 | feed, err := ioutil.ReadFile("chat/feed.html") 467 | if err == nil{ 468 | feedstring = string(feed) 469 | } 470 | //read chatlog (if available) 471 | chatlogstring := "" 472 | chatlog, err := ioutil.ReadFile("chat/chatlog.html") 473 | if err == nil{ 474 | chatlogstring = string(chatlog) 475 | } 476 | //read adminlog 477 | adminlogstring := "" 478 | adminlog, err := ioutil.ReadFile("adminlog") 479 | if err == nil{ 480 | adminlogstring = string(adminlog) 481 | } 482 | 483 | //strip header from chat feed 484 | strings.TrimPrefix(feedstring, feedheader) 485 | //strip footer from chat feed 486 | strings.Replace(feedstring, feedfooter, "", -1) 487 | 488 | //loop over every line in feed, remove all lines of user 489 | cleansedfeed := feedheader 490 | feedlines := strings.Split(feedstring, "
\n") 491 | for _, line := range feedlines{ 492 | linecpy := strings.Split(line, ">")[0]//this does what "strings.trimright" should do but doesn't seem to do! 493 | if strings.Contains(linecpy,noun) == false{ 494 | cleansedfeed += line 495 | cleansedfeed += "
\n" 496 | } 497 | } 498 | cleansedfeed += feedfooter 499 | 500 | //update feed 501 | f, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 502 | if err != nil { 503 | panic(err) 504 | } 505 | defer f.Close() 506 | if _, err = f.WriteString(cleansedfeed); err != nil { 507 | panic(err) 508 | } 509 | f.Sync() 510 | f.Close() 511 | 512 | //loop over every line in chatlog, remove all lines of user 513 | if chatlogstring != ""{ 514 | cleansedchatlog := "" 515 | chatloglines := strings.Split(chatlogstring, "
\n") 516 | for _, line := range chatloglines{ 517 | linecpy := strings.Split(line, ">")[0] 518 | if strings.Contains(linecpy,noun) == false{ 519 | cleansedchatlog += line 520 | cleansedchatlog += "
\n" 521 | } 522 | } 523 | 524 | //update chatlog 525 | cl, err := os.OpenFile("chat/chatlog.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 526 | if err != nil { 527 | panic(err) 528 | } 529 | defer cl.Close() 530 | if _, err = cl.WriteString(cleansedchatlog); err != nil { 531 | panic(err) 532 | } 533 | cl.Sync() 534 | cl.Close() 535 | } 536 | 537 | //get ip of target user 538 | targetip := "" 539 | adminloglines := strings.Split(adminlogstring, "\n") 540 | for _, line := range adminloglines{ 541 | line = strings.Split(line, ">")[0] 542 | if strings.Contains(line,noun){ 543 | targetip = strings.Split(line, " ")[0] 544 | targetip = strings.Replace(targetip, " ", "", -1) 545 | } 546 | } 547 | 548 | //append IP to blockip file 549 | if targetip != ""{ 550 | blockip, err := os.OpenFile("blockip", os.O_APPEND|os.O_WRONLY, 0600) 551 | if err == nil { 552 | appendip := "\n" 553 | appendip += targetip 554 | defer blockip.Close() 555 | if _, err = blockip.WriteString(appendip); err != nil { 556 | panic(err) 557 | } 558 | blockip.Sync() 559 | } 560 | blockip.Close() 561 | } 562 | } 563 | return 1 564 | case "/banstr": //ban users that posted a specific string and delete the posts (botnet spam) 565 | if checkAdminIP(ip) == true && noun != ""{ 566 | //read chat feed 567 | feedstring := "" 568 | feed, err := ioutil.ReadFile("chat/feed.html") 569 | if err == nil{ 570 | feedstring = string(feed) 571 | } 572 | //read chatlog (if available) 573 | chatlogstring := "" 574 | chatlog, err := ioutil.ReadFile("chat/chatlog.html") 575 | if err == nil{ 576 | chatlogstring = string(chatlog) 577 | } 578 | //read adminlog 579 | adminlogstring := "" 580 | adminlog, err := ioutil.ReadFile("adminlog") 581 | if err == nil{ 582 | adminlogstring = string(adminlog) 583 | } 584 | 585 | //strip header from chat feed 586 | strings.TrimPrefix(feedstring, feedheader) 587 | //strip footer from chat feed 588 | strings.Replace(feedstring, feedfooter, "", -1) 589 | 590 | //loop over every line in feed, remove all lines containing string 591 | cleansedfeed := feedheader 592 | feedlines := strings.Split(feedstring, "
\n") 593 | for _, line := range feedlines{ 594 | if strings.Contains(strings.ToLower(line),strings.ToLower(noun)) == false{ 595 | cleansedfeed += line 596 | cleansedfeed += "
\n" 597 | } 598 | } 599 | cleansedfeed += feedfooter 600 | 601 | //update feed 602 | f, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 603 | if err != nil { 604 | panic(err) 605 | } 606 | defer f.Close() 607 | if _, err = f.WriteString(cleansedfeed); err != nil { 608 | panic(err) 609 | } 610 | f.Sync() 611 | f.Close() 612 | 613 | //loop over every line in chatlog, remove all lines containing string 614 | if chatlogstring != ""{ 615 | cleansedchatlog := "" 616 | chatloglines := strings.Split(chatlogstring, "
\n") 617 | for _, line := range chatloglines{ 618 | if strings.Contains(strings.ToLower(line),strings.ToLower(noun)) == false{ 619 | cleansedchatlog += line 620 | cleansedchatlog += "
\n" 621 | } 622 | } 623 | 624 | //update chatlog 625 | cl, err := os.OpenFile("chat/chatlog.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 626 | if err != nil { 627 | panic(err) 628 | } 629 | defer cl.Close() 630 | if _, err = cl.WriteString(cleansedchatlog); err != nil { 631 | panic(err) 632 | } 633 | cl.Sync() 634 | cl.Close() 635 | } 636 | 637 | //get ips of users with target string 638 | targetips := "" 639 | targetip := "" 640 | adminloglines := strings.Split(adminlogstring, "\n") 641 | for _, line := range adminloglines{ 642 | if strings.Contains(strings.ToLower(line),strings.ToLower(noun)){ 643 | targetip = strings.Split(line, " ")[0] 644 | targetip = strings.Replace(targetip, " ", "", -1) 645 | if strings.Contains(targetips,targetip) == false{ 646 | targetips += "\n" 647 | targetips += targetip 648 | } 649 | } 650 | } 651 | 652 | //append IPs to blockip file 653 | if targetips != ""{ 654 | blockip, err := os.OpenFile("blockip", os.O_APPEND|os.O_WRONLY, 0600) 655 | if err == nil { 656 | defer blockip.Close() 657 | if _, err = blockip.WriteString(targetips); err != nil { 658 | panic(err) 659 | } 660 | blockip.Sync() 661 | } 662 | blockip.Close() 663 | } 664 | } 665 | return 1 666 | case "/delstr": //Delete posts that contain string 667 | if checkAdminIP(ip) == true && noun != ""{ 668 | //read chat feed 669 | feedstring := "" 670 | feed, err := ioutil.ReadFile("chat/feed.html") 671 | if err == nil{ 672 | feedstring = string(feed) 673 | } 674 | //read chatlog (if available) 675 | chatlogstring := "" 676 | chatlog, err := ioutil.ReadFile("chat/chatlog.html") 677 | if err == nil{ 678 | chatlogstring = string(chatlog) 679 | } 680 | 681 | //strip header from chat feed 682 | strings.TrimPrefix(feedstring, feedheader) 683 | //strip footer from chat feed 684 | strings.Replace(feedstring, feedfooter, "", -1) 685 | 686 | //loop over every line in feed, remove all lines containing string 687 | cleansedfeed := feedheader 688 | feedlines := strings.Split(feedstring, "
\n") 689 | for _, line := range feedlines{ 690 | if strings.Contains(strings.ToLower(line),strings.ToLower(noun)) == false{ 691 | cleansedfeed += line 692 | cleansedfeed += "
\n" 693 | } 694 | } 695 | cleansedfeed += feedfooter 696 | 697 | //update feed 698 | f, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 699 | if err != nil { 700 | panic(err) 701 | } 702 | defer f.Close() 703 | if _, err = f.WriteString(cleansedfeed); err != nil { 704 | panic(err) 705 | } 706 | f.Sync() 707 | f.Close() 708 | 709 | //loop over every line in chatlog, remove all lines containing string 710 | if chatlogstring != ""{ 711 | cleansedchatlog := "" 712 | chatloglines := strings.Split(chatlogstring, "
\n") 713 | for _, line := range chatloglines{ 714 | if strings.Contains(strings.ToLower(line),strings.ToLower(noun)) == false{ 715 | cleansedchatlog += line 716 | cleansedchatlog += "
\n" 717 | } 718 | } 719 | 720 | //update chatlog 721 | cl, err := os.OpenFile("chat/chatlog.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 722 | if err != nil { 723 | panic(err) 724 | } 725 | defer cl.Close() 726 | if _, err = cl.WriteString(cleansedchatlog); err != nil { 727 | panic(err) 728 | } 729 | cl.Sync() 730 | cl.Close() 731 | } 732 | } 733 | return 1 734 | default: 735 | return 0 736 | } 737 | } 738 | 739 | func createID(ip string, date string) string{ 740 | //take last 3 numbers of client IP, xor it with a key. Just want to make a somewhat unique id that changes daily for users to call each other. 741 | ip = strings.Replace(ip, ".", "", -1) 742 | last3 := len(ip) - 3 743 | ip = ip[last3:len(ip)] 744 | //load key file - remember to set your own 93 byte key string instead of the default 745 | key, err := ioutil.ReadFile("key") 746 | if err != nil { 747 | panic(err) 748 | } 749 | dateint, _ := strconv.Atoi(date) 750 | keystring := string(key) 751 | keystring = keystring[dateint*3-3:dateint*3]//every day of the month gets a new 3 byte key, based on a key string of at least 93 bytes (3 bytes per day of the month up to 31 days) 752 | id := "" 753 | //xor ip with key 754 | for i := 0; i < len(ip); i++ { 755 | id += string(ip[i] ^ keystring[i]) 756 | } 757 | //convert to a number system so ID will render properly 758 | //return hex.EncodeToString([]byte(id)) //going to use base36 instead of base16 for compactness 759 | idstring := string(EncodeBytesAsBytes([]byte(id))) 760 | idstring = idstring[1:len(idstring)] 761 | return idstring 762 | } 763 | 764 | 765 | //-------------------------------------------------------------------------------------------- 766 | //base36 encoder from https://github.com/martinlindhe/base36 767 | 768 | var ( 769 | base36 = []byte{ 770 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 771 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 772 | 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 773 | 'u', 'v', 'w', 'x', 'y', 'z'} 774 | ) 775 | 776 | // EncodeBytesAsBytes encodes a byte slice to base36. 777 | func EncodeBytesAsBytes(b []byte) []byte { 778 | var bigRadix = big.NewInt(36) 779 | var bigZero = big.NewInt(0) 780 | x := new(big.Int) 781 | x.SetBytes(b) 782 | 783 | answer := make([]byte, 0, len(b)*136/100) 784 | for x.Cmp(bigZero) > 0 { 785 | mod := new(big.Int) 786 | x.DivMod(x, bigRadix, mod) 787 | answer = append(answer, base36[mod.Int64()]) 788 | } 789 | 790 | // leading zero bytes 791 | for _, i := range b { 792 | if i != 0 { 793 | break 794 | } 795 | answer = append(answer, base36[0]) 796 | } 797 | 798 | // reverse 799 | alen := len(answer) 800 | for i := 0; i < alen/2; i++ { 801 | answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] 802 | } 803 | 804 | return answer 805 | } 806 | 807 | 808 | -------------------------------------------------------------------------------- /chat/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ghostchat 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ghostchat.go: -------------------------------------------------------------------------------- 1 | //Ghostchat v1.0 2 | //Description: Simple anonymous HTTP chat server, requires no client side scripts. Works well with users on ancient machines. 3 | //Author: Wiby Search Engine 4 | //License: GPL v2 5 | 6 | //A few extra files are needed. Get full source at http://wiby.me/download/ghostchat.zip or https://github.com/wibyweb/ghostchat/ 7 | 8 | //Enter chat: http://serverIP:PORT/chat/ - Hit refresh for it to begin working. Default port is 4444. 9 | 10 | //------------------------------------------------------ 11 | //Admin Commands (Place admin IPs inside 'adminip' file) 12 | //------------------------------------------------------ 13 | //Close chat: /close 14 | //Open chat: /open 15 | //Ban user and delete their posts: /ban userID 16 | //Ban users and delete posts containing string: /banstr string 17 | //Delete posts containing string: /delstr string 18 | //Enable or clear chat log: /log 19 | //Disable and delete chat log: /nolog 20 | //Clear chat feed: /clear 21 | //Change message of the day: /motd message 22 | //Remove message of the day: /motd 23 | //Filter swearwords: Add swearwords to 'swearfilter' file. 24 | //------------------------------------------------------ 25 | 26 | //Note: Anything inside the chat folder is served publicly. 27 | // If cursor does not appear on form after pressing send, press Tab. 28 | // Set a unique 93-byte key in the file called 'key'. Three bytes are used per day to create a 29 | // usually unique ID based on the last 3 numbers of each client IP, ignoring octets. 30 | // Full logs containing client IPs are located inside 'adminlog'. 31 | // USE THIS CHAT SERVER AT YOUR OWN RISK. 32 | 33 | package main 34 | 35 | import ( 36 | "fmt" 37 | "log" 38 | "os" 39 | "net/http" 40 | "io/ioutil" 41 | "strings" 42 | "time" 43 | "strconv" 44 | // "encoding/hex" 45 | "math/big" 46 | ) 47 | 48 | var feedheader string = "\n\n\n\n\n" 49 | var feedfooter string = "\n" 50 | 51 | func main() { 52 | http.HandleFunc("/chat/post",handler) 53 | http.HandleFunc("/", handler) 54 | log.Fatal(http.ListenAndServe(":4444", nil)) 55 | // log.Fatal(http.ListenAndServe("localhost:4444", nil))//For use behind a reverse proxy, make sure to send X-Real-IP header. Disable above line to use this one, also enable line 59 and disable line 60. 56 | } 57 | 58 | func handler(w http.ResponseWriter, r *http.Request) { 59 | //ip := r.Header.Get("X-Real-IP") //For use behind a reverse proxy. Make sure your reverse proxy is configured so header X-Real-IP cannot be spoofed. Also see comments on line 55 60 | ip := r.RemoteAddr[0:strings.LastIndex(r.RemoteAddr,":")] 61 | 62 | if checkban(ip) == false{ 63 | switch r.Method { 64 | case "GET": 65 | http.Handle("/chat/", http.StripPrefix("/chat/", http.FileServer(http.Dir("chat/")))) 66 | 67 | case "POST": 68 | // Call ParseForm() to parse the raw query and update r.PostForm and r.Form. 69 | if err := r.ParseForm(); err != nil { 70 | fmt.Fprintf(w, "ParseForm() err: %v", err) 71 | return 72 | } 73 | 74 | formMessage := r.FormValue("message") 75 | 76 | //check if an admin command was made 77 | command := 0 78 | if formMessage != "" && formMessage[0] == '/'{ 79 | command = commandhandler(formMessage, ip) 80 | } 81 | 82 | maxlines := 13 //max number of lines in feed. (13 is default for classic mac screen) 83 | 84 | //load contents of motd (message of the day) 85 | motdstring := "" 86 | motd, err := ioutil.ReadFile("motd") 87 | if err != nil { 88 | motdstring = "" 89 | } 90 | motdstring = string(motd) 91 | 92 | //load entire contents of feed 93 | feed, err := ioutil.ReadFile("chat/feed.html") 94 | if err != nil { 95 | panic(err) 96 | } 97 | feedstring := string(feed) 98 | 99 | if motdstring != "close\n" && motdstring != "close" && r.FormValue("message") != "" && command == 0{ 100 | formMessageRune := []rune(formMessage) 101 | if len(formMessageRune) > 180{//trim posts greater than 180 runes 102 | formMessageRune = formMessageRune[0:179] 103 | } 104 | formMessage = string(formMessageRune) 105 | formMessage = strings.Replace(formMessage, "\n", "", -1)//user cant insert line feeds 106 | formMessage = swearfilter(formMessage) 107 | thetime := time.Now() 108 | //thetime.Format("2006-01-02 15:04:05") 109 | message := thetime.Format("15:04") 110 | message += " <" 111 | message += createID(ip,thetime.Format("02")) 112 | message += "> " 113 | message += formMessage 114 | message = strings.Replace(message, "<", "<", -1)//user cant insert html 115 | message = strings.Replace(message, ">", ">", -1) 116 | messagerune := []rune(message) 117 | 118 | //readjust lines in feed if motd present 119 | if motdstring != ""{ 120 | motdrune := []rune(motdstring) 121 | if len(motdrune) > 95{ 122 | maxlines -= 1 123 | } 124 | maxlines -= 1 125 | } 126 | 127 | //remove html header from feed 128 | feedstring = strings.Replace(feedstring, feedheader, "", -1) 129 | feedstring = strings.Replace(feedstring, feedfooter, "", -1) 130 | 131 | feedlinecount := 0 132 | messagelinecount := 0 133 | totalfeedlines := 0 134 | 135 | //posted message can take up to 2 lines 136 | if len(messagerune) > 90{ 137 | messagelinecount += 2 138 | }else{ 139 | messagelinecount ++ 140 | } 141 | lines := strings.Split(feedstring, "
\n") 142 | feedtrimmed := "" 143 | rangecount := 1 144 | //find out how many lines there are currently in the feed 145 | for _, line := range lines{ 146 | //max 90 chars per line, and max message size is 180 or 2 lines 147 | linerune := []rune(line) 148 | if len(linerune) > 95{ 149 | totalfeedlines ++ 150 | } 151 | totalfeedlines ++ 152 | } 153 | //trim old lines that wont fit once message is appended 154 | for _, line := range lines{ 155 | //max 90 chars per line, and max message size is 180 or 2 lines 156 | linerune := []rune(line) 157 | if len(linerune) > 95{ 158 | feedlinecount ++ 159 | } 160 | feedlinecount ++ //chat message will not exceed 180chars or 2 lines max 161 | 162 | if(feedlinecount > messagelinecount){ 163 | if totalfeedlines - feedlinecount < maxlines { 164 | feedtrimmed += line 165 | if (len(lines) != rangecount){ 166 | feedtrimmed += "
\n" 167 | } 168 | } 169 | } 170 | rangecount ++ 171 | } 172 | //update the feed 173 | updatefeed := "" 174 | if (feedlinecount + messagelinecount > (maxlines+1)){ 175 | feedtrimmed += message 176 | updatefeed += feedtrimmed 177 | }else{ 178 | if motdstring != "" && feedstring != ""{//have to remove old motd line before appending it again 179 | motdend := strings.Index(feedstring,"
\n") 180 | feedstring = feedstring[motdend+5:len(feedstring)] 181 | } 182 | updatefeed += feedstring 183 | updatefeed += message 184 | } 185 | updatefeed += "
\n" 186 | updatedfeed := feedheader 187 | //if motd exists, append to feed. Make sure your motd isn't longer than 180 characters. 188 | if motdstring != "" { 189 | updatedfeed += motdstring 190 | updatedfeed += "
\n" 191 | } 192 | updatedfeed += updatefeed 193 | updatedfeed += feedfooter 194 | 195 | //write feed 196 | f, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 197 | if err != nil { 198 | panic(err) 199 | } 200 | defer f.Close() 201 | if _, err = f.WriteString(updatedfeed); err != nil { 202 | panic(err) 203 | } 204 | f.Sync() 205 | f.Close() 206 | 207 | //append log 208 | writelog(ip,message) 209 | 210 | //serve chat form again 211 | http.ServeFile(w, r, "chat/form.html") 212 | }else if motdstring == "close\n" || motdstring == "close"{ 213 | fmt.Fprintf(w, "The chat is currently closed.") 214 | }else{ 215 | //serve chat form again 216 | http.ServeFile(w, r, "chat/form.html") 217 | } 218 | 219 | default: 220 | fmt.Fprintf(w, "Only GET and POST is supported.") 221 | } 222 | }else{//Note that GET requests will still continue working until server is restarted or user restarts 223 | fmt.Fprintf(w, "403 Forbidden") 224 | } 225 | } 226 | 227 | func swearfilter(s string) string{ 228 | swearwords := "" 229 | swearfilter, err := ioutil.ReadFile("swearfilter") 230 | if err != nil { 231 | swearwords = "" 232 | }else{ 233 | swearwords = string(swearfilter) 234 | } 235 | swearwords = strings.Replace(swearwords, "\r", "", -1)//some editors like to insert carriage returns with line feeds 236 | if(len(swearwords) > 0){ 237 | if swearwords[len(swearwords)-1] == byte('\n'){//some text editors like to do this at the final byte, can't have that 238 | swearwords = swearwords[0:len(swearwords)-1] 239 | } 240 | swears := strings.Split(swearwords, "\n") 241 | sLower := strings.ToLower(s) 242 | for _, swearword := range swears{ 243 | if strings.Contains(sLower,swearword){ 244 | return "I have a potty mouth." 245 | } 246 | } 247 | } 248 | return s 249 | } 250 | 251 | func checkban(ip string) bool{ 252 | //fmt.Printf("\n%s",ip) 253 | //load entire contents of blockip 254 | blockip, err := ioutil.ReadFile("blockip") 255 | if err != nil { 256 | panic(err) 257 | } 258 | blockipstring := string(blockip) 259 | blockipstring = strings.Replace(blockipstring, "\r", "", -1) 260 | lines := strings.Split(blockipstring, "\n") 261 | for _, line := range lines{ 262 | if ip == line{ 263 | return true 264 | } 265 | } 266 | return false 267 | } 268 | 269 | func checkAdminIP(ip string) bool{ 270 | adminip, err := ioutil.ReadFile("adminip") 271 | if err != nil { 272 | panic(err) 273 | } 274 | adminipstring := string(adminip) 275 | adminipstring = strings.Replace(adminipstring, "\r", "", -1) 276 | lines := strings.Split(adminipstring, "\n") 277 | for _, line := range lines{ 278 | if ip == line{ 279 | return true 280 | } 281 | } 282 | return false 283 | } 284 | 285 | func writelog(ip string, message string){ 286 | logmessage := ip 287 | logmessage += " " 288 | logmessage += message 289 | logmessage += "\n" 290 | flog, err := os.OpenFile("adminlog", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) 291 | if err != nil { 292 | panic(err) 293 | } 294 | defer flog.Close() 295 | if _, err = flog.WriteString(logmessage); err != nil { 296 | panic(err) 297 | } 298 | flog.Sync() 299 | flog.Close() 300 | 301 | //appends to a public chat log (chatlog.html) but only if you create this file yourself 302 | fchatlog, err := os.OpenFile("chat/chatlog.html", os.O_APPEND|os.O_WRONLY, 0600) 303 | if err == nil { 304 | message += "
\n" 305 | defer fchatlog.Close() 306 | if _, err = fchatlog.WriteString(message); err != nil { 307 | panic(err) 308 | } 309 | fchatlog.Sync() 310 | } 311 | fchatlog.Close() 312 | } 313 | 314 | func commandhandler(formMessage string, ip string) int{ 315 | noun := "" 316 | if len(formMessage) > 6 && formMessage[0:6] == "/motd "{ 317 | noun = strings.TrimPrefix(formMessage, "/motd ") 318 | formMessage = "/motd" 319 | } 320 | if len(formMessage) > 5 && formMessage[0:5] == "/ban "{ 321 | noun = strings.TrimPrefix(formMessage, "/ban ") 322 | formMessage = "/ban" 323 | } 324 | if len(formMessage) > 8 && formMessage[0:8] == "/banstr "{ 325 | noun = strings.TrimPrefix(formMessage, "/banstr ") 326 | formMessage = "/banstr" 327 | } 328 | if len(formMessage) > 8 && formMessage[0:8] == "/delstr "{ 329 | noun = strings.TrimPrefix(formMessage, "/delstr ") 330 | formMessage = "/delstr" 331 | } 332 | switch formMessage{ //process any verbs 333 | case "/close": //close the chat 334 | if checkAdminIP(ip) == true{ 335 | motdstring := "" 336 | //read motd 337 | motd, err := ioutil.ReadFile("motd") 338 | if err == nil{ 339 | motdstring = string(motd) 340 | } 341 | if motdstring != "close"{ 342 | //backup motd 343 | motdbak, err := os.OpenFile("motdbak", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 344 | if err != nil { 345 | panic(err) 346 | } 347 | defer motdbak.Close() 348 | if _, err = motdbak.WriteString(motdstring); err != nil { 349 | panic(err) 350 | } 351 | motdbak.Sync() 352 | motdbak.Close() 353 | 354 | //write "close" to motd 355 | motdupdate, err := os.OpenFile("motd", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 356 | if err != nil { 357 | panic(err) 358 | } 359 | defer motdupdate.Close() 360 | if _, err = motdupdate.WriteString("close"); err != nil { 361 | panic(err) 362 | } 363 | motdupdate.Sync() 364 | motdupdate.Close() 365 | 366 | //update feed to stop the auto refresh 367 | feed, err := ioutil.ReadFile("chat/feed.html") 368 | if err != nil { 369 | panic(err) 370 | } 371 | feedstring := string(feed) 372 | feedstring = strings.Replace(feedstring, "http-equiv=\"refresh\" content=", "", -1) 373 | f, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 374 | if err != nil { 375 | panic(err) 376 | } 377 | defer f.Close() 378 | if _, err = f.WriteString(feedstring); err != nil { 379 | panic(err) 380 | } 381 | f.Sync() 382 | f.Close() 383 | } 384 | } 385 | return 0 386 | case "/open": //open the chat 387 | if checkAdminIP(ip) == true{ 388 | motdstring := "" 389 | //read motd 390 | motd, err := ioutil.ReadFile("motd") 391 | if err == nil{ 392 | motdstring = string(motd) 393 | } 394 | if motdstring == "close"{ 395 | //read motdbak 396 | motdbak, err := ioutil.ReadFile("motdbak") 397 | if err != nil { 398 | motdstring = "" 399 | }else{ 400 | motdstring = string(motdbak) 401 | } 402 | 403 | //restore motd 404 | motdrestore, err := os.OpenFile("motd", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 405 | if err != nil { 406 | panic(err) 407 | } 408 | defer motdrestore.Close() 409 | if _, err = motdrestore.WriteString(motdstring); err != nil { 410 | panic(err) 411 | } 412 | motdrestore.Sync() 413 | motdrestore.Close() 414 | } 415 | } 416 | return 0 417 | case "/log": //enable or clear chatlog 418 | if checkAdminIP(ip) == true{ 419 | chatlog, err := os.OpenFile("chat/chatlog.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 420 | if err != nil { 421 | panic(err) 422 | } 423 | chatlog.Close() 424 | } 425 | return 1 426 | case "/nolog": //delete and disable chatlog 427 | if checkAdminIP(ip) == true{ 428 | err := os.Remove("chat/chatlog.html") 429 | if err != nil { 430 | fmt.Printf("\nChatlog already disabled.") 431 | } 432 | } 433 | return 1 434 | case "/clear": //clear the feed 435 | if checkAdminIP(ip) == true{ 436 | feed, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 437 | if err != nil { 438 | panic(err) 439 | } 440 | defer feed.Close() 441 | if _, err = feed.WriteString(feedheader + feedfooter); err != nil { 442 | panic(err) 443 | } 444 | feed.Sync() 445 | feed.Close() 446 | } 447 | return 1 448 | case "/motd": //update motd - not adding a message will remove motd 449 | if checkAdminIP(ip) == true{ 450 | motdupdate, err := os.OpenFile("motd", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 451 | if err != nil { 452 | panic(err) 453 | } 454 | defer motdupdate.Close() 455 | if _, err = motdupdate.WriteString(noun); err != nil { 456 | panic(err) 457 | } 458 | motdupdate.Sync() 459 | motdupdate.Close() 460 | } 461 | return 0 462 | case "/ban": //ban a user and remove their prior posts 463 | if checkAdminIP(ip) == true && noun != ""{ 464 | //read chat feed 465 | feedstring := "" 466 | feed, err := ioutil.ReadFile("chat/feed.html") 467 | if err == nil{ 468 | feedstring = string(feed) 469 | } 470 | //read chatlog (if available) 471 | chatlogstring := "" 472 | chatlog, err := ioutil.ReadFile("chat/chatlog.html") 473 | if err == nil{ 474 | chatlogstring = string(chatlog) 475 | } 476 | //read adminlog 477 | adminlogstring := "" 478 | adminlog, err := ioutil.ReadFile("adminlog") 479 | if err == nil{ 480 | adminlogstring = string(adminlog) 481 | } 482 | 483 | //strip header from chat feed 484 | strings.TrimPrefix(feedstring, feedheader) 485 | //strip footer from chat feed 486 | strings.Replace(feedstring, feedfooter, "", -1) 487 | 488 | //loop over every line in feed, remove all lines of user 489 | cleansedfeed := feedheader 490 | feedlines := strings.Split(feedstring, "
\n") 491 | for _, line := range feedlines{ 492 | linecpy := strings.Split(line, ">")[0]//this does what "strings.trimright" should do but doesn't seem to do! 493 | if strings.Contains(linecpy,noun) == false{ 494 | cleansedfeed += line 495 | cleansedfeed += "
\n" 496 | } 497 | } 498 | cleansedfeed += feedfooter 499 | 500 | //update feed 501 | f, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 502 | if err != nil { 503 | panic(err) 504 | } 505 | defer f.Close() 506 | if _, err = f.WriteString(cleansedfeed); err != nil { 507 | panic(err) 508 | } 509 | f.Sync() 510 | f.Close() 511 | 512 | //loop over every line in chatlog, remove all lines of user 513 | if chatlogstring != ""{ 514 | cleansedchatlog := "" 515 | chatloglines := strings.Split(chatlogstring, "
\n") 516 | for _, line := range chatloglines{ 517 | linecpy := strings.Split(line, ">")[0] 518 | if strings.Contains(linecpy,noun) == false{ 519 | cleansedchatlog += line 520 | cleansedchatlog += "
\n" 521 | } 522 | } 523 | 524 | //update chatlog 525 | cl, err := os.OpenFile("chat/chatlog.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 526 | if err != nil { 527 | panic(err) 528 | } 529 | defer cl.Close() 530 | if _, err = cl.WriteString(cleansedchatlog); err != nil { 531 | panic(err) 532 | } 533 | cl.Sync() 534 | cl.Close() 535 | } 536 | 537 | //get ip of target user 538 | targetip := "" 539 | adminloglines := strings.Split(adminlogstring, "\n") 540 | for _, line := range adminloglines{ 541 | line = strings.Split(line, ">")[0] 542 | if strings.Contains(line,noun){ 543 | targetip = strings.Split(line, " ")[0] 544 | targetip = strings.Replace(targetip, " ", "", -1) 545 | } 546 | } 547 | 548 | //append IP to blockip file 549 | if targetip != ""{ 550 | blockip, err := os.OpenFile("blockip", os.O_APPEND|os.O_WRONLY, 0600) 551 | if err == nil { 552 | appendip := "\n" 553 | appendip += targetip 554 | defer blockip.Close() 555 | if _, err = blockip.WriteString(appendip); err != nil { 556 | panic(err) 557 | } 558 | blockip.Sync() 559 | } 560 | blockip.Close() 561 | } 562 | } 563 | return 1 564 | case "/banstr": //ban users that posted a specific string and delete the posts (botnet spam) 565 | if checkAdminIP(ip) == true && noun != ""{ 566 | //read chat feed 567 | feedstring := "" 568 | feed, err := ioutil.ReadFile("chat/feed.html") 569 | if err == nil{ 570 | feedstring = string(feed) 571 | } 572 | //read chatlog (if available) 573 | chatlogstring := "" 574 | chatlog, err := ioutil.ReadFile("chat/chatlog.html") 575 | if err == nil{ 576 | chatlogstring = string(chatlog) 577 | } 578 | //read adminlog 579 | adminlogstring := "" 580 | adminlog, err := ioutil.ReadFile("adminlog") 581 | if err == nil{ 582 | adminlogstring = string(adminlog) 583 | } 584 | 585 | //strip header from chat feed 586 | strings.TrimPrefix(feedstring, feedheader) 587 | //strip footer from chat feed 588 | strings.Replace(feedstring, feedfooter, "", -1) 589 | 590 | //loop over every line in feed, remove all lines containing string 591 | cleansedfeed := feedheader 592 | feedlines := strings.Split(feedstring, "
\n") 593 | for _, line := range feedlines{ 594 | if strings.Contains(strings.ToLower(line),strings.ToLower(noun)) == false{ 595 | cleansedfeed += line 596 | cleansedfeed += "
\n" 597 | } 598 | } 599 | cleansedfeed += feedfooter 600 | 601 | //update feed 602 | f, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 603 | if err != nil { 604 | panic(err) 605 | } 606 | defer f.Close() 607 | if _, err = f.WriteString(cleansedfeed); err != nil { 608 | panic(err) 609 | } 610 | f.Sync() 611 | f.Close() 612 | 613 | //loop over every line in chatlog, remove all lines containing string 614 | if chatlogstring != ""{ 615 | cleansedchatlog := "" 616 | chatloglines := strings.Split(chatlogstring, "
\n") 617 | for _, line := range chatloglines{ 618 | if strings.Contains(strings.ToLower(line),strings.ToLower(noun)) == false{ 619 | cleansedchatlog += line 620 | cleansedchatlog += "
\n" 621 | } 622 | } 623 | 624 | //update chatlog 625 | cl, err := os.OpenFile("chat/chatlog.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 626 | if err != nil { 627 | panic(err) 628 | } 629 | defer cl.Close() 630 | if _, err = cl.WriteString(cleansedchatlog); err != nil { 631 | panic(err) 632 | } 633 | cl.Sync() 634 | cl.Close() 635 | } 636 | 637 | //get ips of users with target string 638 | targetips := "" 639 | targetip := "" 640 | adminloglines := strings.Split(adminlogstring, "\n") 641 | for _, line := range adminloglines{ 642 | if strings.Contains(strings.ToLower(line),strings.ToLower(noun)){ 643 | targetip = strings.Split(line, " ")[0] 644 | targetip = strings.Replace(targetip, " ", "", -1) 645 | if strings.Contains(targetips,targetip) == false{ 646 | targetips += "\n" 647 | targetips += targetip 648 | } 649 | } 650 | } 651 | 652 | //append IPs to blockip file 653 | if targetips != ""{ 654 | blockip, err := os.OpenFile("blockip", os.O_APPEND|os.O_WRONLY, 0600) 655 | if err == nil { 656 | defer blockip.Close() 657 | if _, err = blockip.WriteString(targetips); err != nil { 658 | panic(err) 659 | } 660 | blockip.Sync() 661 | } 662 | blockip.Close() 663 | } 664 | } 665 | return 1 666 | case "/delstr": //Delete posts that contain string 667 | if checkAdminIP(ip) == true && noun != ""{ 668 | //read chat feed 669 | feedstring := "" 670 | feed, err := ioutil.ReadFile("chat/feed.html") 671 | if err == nil{ 672 | feedstring = string(feed) 673 | } 674 | //read chatlog (if available) 675 | chatlogstring := "" 676 | chatlog, err := ioutil.ReadFile("chat/chatlog.html") 677 | if err == nil{ 678 | chatlogstring = string(chatlog) 679 | } 680 | 681 | //strip header from chat feed 682 | strings.TrimPrefix(feedstring, feedheader) 683 | //strip footer from chat feed 684 | strings.Replace(feedstring, feedfooter, "", -1) 685 | 686 | //loop over every line in feed, remove all lines containing string 687 | cleansedfeed := feedheader 688 | feedlines := strings.Split(feedstring, "
\n") 689 | for _, line := range feedlines{ 690 | if strings.Contains(strings.ToLower(line),strings.ToLower(noun)) == false{ 691 | cleansedfeed += line 692 | cleansedfeed += "
\n" 693 | } 694 | } 695 | cleansedfeed += feedfooter 696 | 697 | //update feed 698 | f, err := os.OpenFile("chat/feed.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 699 | if err != nil { 700 | panic(err) 701 | } 702 | defer f.Close() 703 | if _, err = f.WriteString(cleansedfeed); err != nil { 704 | panic(err) 705 | } 706 | f.Sync() 707 | f.Close() 708 | 709 | //loop over every line in chatlog, remove all lines containing string 710 | if chatlogstring != ""{ 711 | cleansedchatlog := "" 712 | chatloglines := strings.Split(chatlogstring, "
\n") 713 | for _, line := range chatloglines{ 714 | if strings.Contains(strings.ToLower(line),strings.ToLower(noun)) == false{ 715 | cleansedchatlog += line 716 | cleansedchatlog += "
\n" 717 | } 718 | } 719 | 720 | //update chatlog 721 | cl, err := os.OpenFile("chat/chatlog.html", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0600) 722 | if err != nil { 723 | panic(err) 724 | } 725 | defer cl.Close() 726 | if _, err = cl.WriteString(cleansedchatlog); err != nil { 727 | panic(err) 728 | } 729 | cl.Sync() 730 | cl.Close() 731 | } 732 | } 733 | return 1 734 | default: 735 | return 0 736 | } 737 | } 738 | 739 | func createID(ip string, date string) string{ 740 | //take last 3 numbers of client IP, xor it with a key. Just want to make a somewhat unique id that changes daily for users to call each other. 741 | ip = strings.Replace(ip, ".", "", -1) 742 | last3 := len(ip) - 3 743 | ip = ip[last3:len(ip)] 744 | //load key file - remember to set your own 93 byte key string instead of the default 745 | key, err := ioutil.ReadFile("key") 746 | if err != nil { 747 | panic(err) 748 | } 749 | dateint, _ := strconv.Atoi(date) 750 | keystring := string(key) 751 | keystring = keystring[dateint*3-3:dateint*3]//every day of the month gets a new 3 byte key, based on a key string of at least 93 bytes (3 bytes per day of the month up to 31 days) 752 | id := "" 753 | //xor ip with key 754 | for i := 0; i < len(ip); i++ { 755 | id += string(ip[i] ^ keystring[i]) 756 | } 757 | //convert to a number system so ID will render properly 758 | //return hex.EncodeToString([]byte(id)) //going to use base36 instead of base16 for compactness 759 | idstring := string(EncodeBytesAsBytes([]byte(id))) 760 | idstring = idstring[1:len(idstring)] 761 | return idstring 762 | } 763 | 764 | 765 | //-------------------------------------------------------------------------------------------- 766 | //base36 encoder from https://github.com/martinlindhe/base36 767 | 768 | var ( 769 | base36 = []byte{ 770 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 771 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 772 | 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 773 | 'u', 'v', 'w', 'x', 'y', 'z'} 774 | ) 775 | 776 | // EncodeBytesAsBytes encodes a byte slice to base36. 777 | func EncodeBytesAsBytes(b []byte) []byte { 778 | var bigRadix = big.NewInt(36) 779 | var bigZero = big.NewInt(0) 780 | x := new(big.Int) 781 | x.SetBytes(b) 782 | 783 | answer := make([]byte, 0, len(b)*136/100) 784 | for x.Cmp(bigZero) > 0 { 785 | mod := new(big.Int) 786 | x.DivMod(x, bigRadix, mod) 787 | answer = append(answer, base36[mod.Int64()]) 788 | } 789 | 790 | // leading zero bytes 791 | for _, i := range b { 792 | if i != 0 { 793 | break 794 | } 795 | answer = append(answer, base36[0]) 796 | } 797 | 798 | // reverse 799 | alen := len(answer) 800 | for i := 0; i < alen/2; i++ { 801 | answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i] 802 | } 803 | 804 | return answer 805 | } 806 | 807 | 808 | -------------------------------------------------------------------------------- /key: -------------------------------------------------------------------------------- 1 | qwegfdsguyfhfdfdghfgjtyt56765t56TG#3grgDFD#$$^$^#gFG*$%ghfhER23236&^%Hghfgbqui4436768f43fvrf) 2 | -------------------------------------------------------------------------------- /motd: -------------------------------------------------------------------------------- 1 | Welcome to Ghostchat! Please be polite. Tip: Press Tab if no cursor. 2 | -------------------------------------------------------------------------------- /swearfilter: -------------------------------------------------------------------------------- 1 | javascript 2 | node.js 3 | bootstrap --------------------------------------------------------------------------------