v1.10.2 patches: continent persistence, city markers, dynamic map center
C changes: - gstorage.c: encode continent into root key for --persist --restore survival - gholder.c: decode continent from root + lat/lng storage - commons.h: add lat/lng fields to GMetrics struct - geoip2.c/geoip1.h: extract coordinates from MMDB - json.c: output lat/lng in geolocation JSON JS changes: - Remove cityIndex guard for static HTML city dots - TopoJSON lat/lng fallback for city coordinates - updateMap fallback city collection - Dynamic orthographic center based on visitor count - Mercator stays at [0,15] for full world view Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
GoAccess was designed and developed by Gerardo Orellana <goaccess@prosoftcorp.com>
|
||||
|
||||
Special thanks to the following individuals for their great contributions:
|
||||
|
||||
* 0bi-w6n-K3nobi <71027865+0bi-w6n-K3nob@users.noreply.github.com>
|
||||
* Aaditya Bagga <noreply@github.com>
|
||||
* abgit <francisco.j.azevedo@gmail.com>
|
||||
* Abhishek Deshpande <abhishek@Abhisheks-MacBook-Air-2.local>
|
||||
* Adam Monsen <haircut@gmail.com>
|
||||
* Adam Števko <adam.stevko@gmail.com>
|
||||
* Adam Weinberger <adamw@adamw.org>
|
||||
* Adrian <totcha@gazeta.pl>
|
||||
* Ainsley Ellis <ainsleymae@proton.me>
|
||||
* aishikoyo <aixxx033@umn.edu>
|
||||
* Alan Placidina <noreply@github.com>
|
||||
* Alexander Eifler <noreply@github.com>
|
||||
* Alexandre GUIOT--VALENTIN <contact@alexandregv.fr>
|
||||
* Alexandre Perrin <noreply@github.com>
|
||||
* Alexis Lefebvre <alexislefebvre+github@gmail.com>
|
||||
* Ali Homafar <home.isfar@gmail.com>
|
||||
* allofmex <allofmex@web.de>
|
||||
* Amos Hayes <ahayes@gcrc.carleton.ca>
|
||||
* A. Nackov <anackov@gmail.com>
|
||||
* Anders Johansson <47452862+tellustheguru@users.noreply.github.com>
|
||||
* Andreas Sommer <andreas.sommer@ppro.com>
|
||||
* Andreas Weigel <andreaswe@securepoint.de>
|
||||
* Andrew Kvalheim <Andrew@Kvalhe.im>
|
||||
* Andrew Minion <andrew@andrewrminion.com>
|
||||
* Antonio Terceiro <terceiro@softwarelivre.org>
|
||||
* Arnaud Rebillout <elboulangero@gmail.com>
|
||||
* Arnie97 <arnie97@gmail.com>
|
||||
* as0n <n.was.here@gmail.com>
|
||||
* Aslak Raanes <noreply@github.com>
|
||||
* Axel Wehner <mail@axelwehner.de>
|
||||
* azio7 <azio7@outlook.com>
|
||||
* bbbboom <bwdyxg@gmail.com>
|
||||
* Bbertatum <bertrand@2backup.fr>
|
||||
* Benjamin Bach <benjamin@overtag.dk>
|
||||
* Bjørnar Hansen <noreply@github.com>
|
||||
* Bob Black <git@riblack.com>
|
||||
* Bo Cai <charpty@gmail.com>
|
||||
* Brandon Coleman <metrix1978@gmail.com>
|
||||
* Branimir Ri\v{c}ko <rickobranimir@gmail.com>
|
||||
* Carlos Duelo <noreply@github.com>
|
||||
* Celso Providelo <celso.providelo@gmail.com>
|
||||
* ChangMo Yang <dcyang@unist.ac.kr>
|
||||
* Chang Zhao <noreply@github.com>
|
||||
* Chilledheart <rwindz0@gmail.com>
|
||||
* Chris Downs <noreply@github.com>
|
||||
* Christian Göttsche <cgzones@googlemail.com>
|
||||
* Christian Hermann <c.hermann@bitbeans.de>
|
||||
* Christian Moelders <christian.moelders@storeplus.de>
|
||||
* Christopher Meng <i@cicku.me>
|
||||
* Clément Hermann <nodens@nodens.org>
|
||||
* cristianpb <felipebrokate@gmail.com>
|
||||
* Cthulhux <github@tuxproject.de>
|
||||
* Daniel Aleksandersen <code@daniel.priv.no>
|
||||
* Daniel Aleksandersen <noreply@github.com>
|
||||
* Daniel (dmilith) Dettlaff <dmilith@me.com>
|
||||
* Danila Vershinin <noreply@github.com>
|
||||
* Danny Kirkham <danny@kirkham.dev>
|
||||
* Darek Kay <hello@darekkay.com>
|
||||
* David Carlier <devnexen@gmail.com>
|
||||
* David Geistert <d3f3kt@scripts4u.net>
|
||||
* d_dandrew <dragonandy@foxmail.com>
|
||||
* ElXreno <elxreno@gmail.com>
|
||||
* Enrique Becerra <kabeza@gmail.com>
|
||||
* evitalis <evitalis@users.noreply.github.com>
|
||||
* Felix Häberle <34959078+felixhaeberle@users.noreply.github.com>
|
||||
* Florian Forster <ff@octo.it>
|
||||
* forDream <noreply@github.com>
|
||||
* fqbuild <fqbuild@FutureQuest.net>
|
||||
* Frederic Cambus <fcambus@users.sourceforge.net>
|
||||
* Func <Funcer@outlook.com>
|
||||
* gemmaro <gemmaro.dev@gmail.com>
|
||||
* Genki Sugawara <sugawara@cookpad.com>
|
||||
* Gerald Combs <gerald@zing.org>
|
||||
* Geraldo Alves <junior.alves.2007@gmail.com>
|
||||
* gitqlt <noreply@github.com>
|
||||
* Hiroki Kamino <46459949+err931@users.noreply.github.com>
|
||||
* holys <chendahui007@gmail.com>
|
||||
* Izzy <izzy@qumran.org>
|
||||
* JackDesBwa <jackdesbwa@gmail.com>
|
||||
* Jannes Blobel <72493222+jannesblobel@users.noreply.github.com>
|
||||
* Jeffery Wilkins <goaccess@prosoftcorp.com>
|
||||
* Jeremy Burks <jeremy@zomgwat.com>
|
||||
* Jeremy Lin <jeremy.lin@gmail.com>
|
||||
* Joaquín de la Zerda <joaquin@itbaf.com>
|
||||
* Joe Groocock <me@frebib.net>
|
||||
* Joe Winett <noreply@github.com>
|
||||
* Jonas Kittner <jonas.kittner@rub.de>
|
||||
* Joona <joona.paivahonka@gmail.com>
|
||||
* Jordan Trask <jtrask@gmail.com>
|
||||
* Josh Briggs <josh@briggsdev.us>
|
||||
* Julian Xhokaxhiu <xhokaxhiujulian@gmail.com>
|
||||
* Justin Mills <justin.mills@utah.edu>
|
||||
* Kamino Hiroki <37243867+4f8p@users.noreply.github.com>
|
||||
* Kit Westneat <kit.westneat@gmail.com>
|
||||
* Koichiro Iwao <meta@vmeta.jp>
|
||||
* kokke <spam@rowdy.dk>
|
||||
* kyle sloan <kyle@ifsight.com>
|
||||
* LeoAttn <leo.massonnat@hotmail.fr>
|
||||
* lhywk <lhywkd22@gmail.com>
|
||||
* Linus Gasser <linus.gasser@epfl.ch>
|
||||
* Magnus Groß <magnus@mggross.com>
|
||||
* Maksim Losev <mlosev@beget.ru>
|
||||
* mario-donnarumma <mario.donnarumma.97@gmail.com>
|
||||
* markiewb <noreply@github.com>
|
||||
* Mark J. Berger <mark.berger.j@gmail.com>
|
||||
* Martins Polakovs <martins.polakovs@gmail.com>
|
||||
* Massimiliano Torromeo <massimiliano.torromeo@gmail.com>
|
||||
* Mathieu Aubin <noreply@github.com>
|
||||
* Mathieu Thoretton <mathieu.thoretton@getyourguide.com>
|
||||
* Max Christian Pohle <goaccess@prosoftcorp.com>
|
||||
* metrix78 <metrix1978@gmail.com>
|
||||
* Michael Vetter <jubalh@iodoru.org>
|
||||
* Mika Raunio <mika@diago.global>
|
||||
* Moritz Schott <unsafe@nurfuerspam.de>
|
||||
* m-r-r <raybaudroigm@gmail.com>
|
||||
* mynameiscfed <christopherfederico@gmail.com>
|
||||
* Newbe36524 <newbe36524@qq.com>
|
||||
* Nicolas Le Manchet <nicolas@lemanchet.fr>
|
||||
* Nicolas <noreply@github.com>
|
||||
* Ophir LOJKINE <pere.jobs@gmail.com>
|
||||
* Otto Kekäläinen <otto.kekalainen@seravo.fi>
|
||||
* Panos Stavrianos <panos-stavrianos@users.noreply.github.com>
|
||||
* pitilux <pitilux@elinar.fr>
|
||||
* Pixelcode <52963327+realpixelcode@users.noreply.github.com>
|
||||
* Placidina <alanplacidina@gmail.com>
|
||||
* pravdomil <noreply@github.com>
|
||||
* rachid-debu <rachid.debu@yahoo.fr>
|
||||
* radoslawc <radoslawc@gmail.com>
|
||||
* rahra <bf@abenteuerland.at>
|
||||
* Ramires Viana <59319979+ramiresviana@users.noreply.github.com>
|
||||
* rgriebl <robert@griebl.org>
|
||||
* Roy Marples <roy@marples.name>
|
||||
* rtmkrlv <artyom.karlov@gmail.com>
|
||||
* Ryow <ryow@midgen.asia>
|
||||
* schoonc <noreply@github.com>
|
||||
* Sean Cross <sean@xobs.io>
|
||||
* Sean Wei <me@sean.taipei>
|
||||
* Sebastian Wiedenroth <wiedi@frubar.net>
|
||||
* sh4rkman <maxime.boussard@gmail.com>
|
||||
* Simon Gardling <Titaniumtown@gmail.com>
|
||||
* SjonHortensius <noreply@github.com>
|
||||
* Steely Wing <noreply@github.com>
|
||||
* Stéphane Péchard <stephanepechard@gmail.com>
|
||||
* Stephen Wade <stephen@stephenwade.me>
|
||||
* Stoyan Dimov <s.dimov@codelogic.eu>
|
||||
* Stuart Henderson <noreply@github.com>
|
||||
* Sveinbjorn Thordarson <sveinbjorn@sveinbjorn.org>
|
||||
* Tatsuyuki Ishi <noreply@github.com>
|
||||
* Thomas Gläßle <t_glaessle@gmx.de>
|
||||
* Thomas Jost <schnouki@schnouki.net>
|
||||
* Thomas Lange <code@nerdmind.de>
|
||||
* throwaway1037 <noreply@github.com>
|
||||
* Tim Gates <tim.gates@iress.com>
|
||||
* Timothy Quilling <teqed@shatteredsky.net>
|
||||
* Tom Samstag <github@modtwo.com>
|
||||
* ugola <urvika.gola@gmail.com>
|
||||
* Ulrich Schwarz <ulmi@absatzen.de>
|
||||
* Viktor Szépe <noreply@github.com>
|
||||
* Viktor Szépe <viktor@szepe.net>
|
||||
* Ville Skyttä <ville.skytta@iki.fi>
|
||||
* Vincent Bernat <vincent@bernat.ch>
|
||||
* Vladimir Pavljuchenkov <spiderx@spiderx.dp.ua>
|
||||
* William Muir <wamuir@gmail.com>
|
||||
* Wladimir Palant <noreply@github.com>
|
||||
* wodev <wodev@fischer-net.net>
|
||||
* woobee <support.developpement@gmail.com>
|
||||
* Yaroslav Halchenko <debian@onerussian.com>
|
||||
* Yuri D'Elia <wavexx@thregr.org>
|
||||
* Yuriy M. Kaminskiy <yumkam@gmail.com>
|
||||
* zeke <zhenkai0605@gmail.com>
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2009-2022 Gerardo Orellana <hello @ goaccess.io>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
# Docker image features
|
||||
|
||||
* This command uses the language set for this system. If that does not support it will be output in English. [**Supported Language**](https://github.com/allinurl/goaccess/raw/master/po/LINGUAS)
|
||||
|
||||
* This image supports building on the ARM architecture (e.g. Raspberry Pi)
|
||||
|
||||
* Do you want to change the timezone? Use the `-e` option to pass the time-zone setting to Docker. (e.g. `-e TZ="America/New_York"`)
|
||||
|
||||
* The container is built with geo-location support (see [the manual](https://goaccess.io/man#options)). To enable the respective panel, mount the geolocation database using `-v /path/to/GeoLite2-City.mmdb:/GeoLite2-City.mmdb` and specify `--geoip-database /GeoLite2-City.mmdb` when running GoAccess.
|
||||
|
||||
* If you made changes to the config file after building the image, you don't have to rebuild from scratch. Simply restart the container:
|
||||
|
||||
```
|
||||
docker restart goaccess
|
||||
```
|
||||
|
||||
* If you had already run the container, you may have to stop and remove it first:
|
||||
|
||||
```
|
||||
docker stop goaccess
|
||||
docker rm goaccess
|
||||
```
|
||||
|
||||
* The container and image can be completely removed as follows:
|
||||
|
||||
```
|
||||
docker stop goaccess
|
||||
docker rm goaccess
|
||||
docker rmi allinurl/goaccess
|
||||
```
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Build stage
|
||||
FROM alpine:3.20 AS builds
|
||||
RUN apk add --no-cache \
|
||||
autoconf \
|
||||
automake \
|
||||
build-base \
|
||||
clang \
|
||||
gettext-dev \
|
||||
libmaxminddb-dev \
|
||||
openssl-dev \
|
||||
linux-headers \
|
||||
ncurses-dev \
|
||||
pkgconf \
|
||||
tzdata
|
||||
|
||||
# GoAccess
|
||||
COPY . /goaccess
|
||||
WORKDIR /goaccess
|
||||
RUN autoreconf -fiv && rm -rf autom4te.cache
|
||||
RUN CC="clang" CFLAGS="-O3" LIBS="$(pkg-config --libs openssl)" ./configure --prefix=/usr --enable-utf8 --with-openssl --enable-geoip=mmdb
|
||||
RUN make -j$(nproc) && make DESTDIR=/dist install
|
||||
# Check dynamic dependencies
|
||||
RUN ldd /dist/usr/bin/goaccess && echo "Dependencies checked"
|
||||
|
||||
# Runtime stage
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache \
|
||||
gettext-libs \
|
||||
libmaxminddb \
|
||||
ncurses-libs \
|
||||
openssl \
|
||||
tzdata
|
||||
# Copy GoAccess binary and assets
|
||||
COPY --from=builds /dist/usr/bin/goaccess /usr/bin/goaccess
|
||||
COPY --from=builds /dist/usr/share /usr/share
|
||||
COPY --from=builds /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
# Set up volume and port
|
||||
VOLUME /var/www/goaccess
|
||||
EXPOSE 7890
|
||||
ENTRYPOINT ["/usr/bin/goaccess"]
|
||||
CMD ["--help"]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Used to have all compile dependencies isolated in a container image.
|
||||
FROM debian:12
|
||||
|
||||
RUN apt update -qqq
|
||||
RUN apt install -y \
|
||||
libncurses-dev \
|
||||
libssl-dev \
|
||||
libmaxminddb-dev \
|
||||
build-essential \
|
||||
autoconf \
|
||||
gettext \
|
||||
autopoint
|
||||
|
||||
# GoAccess
|
||||
WORKDIR /goaccess
|
||||
|
||||
ENTRYPOINT ["./build-dynamic.sh"]
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
#AUTOMAKE_OPTIONS = foreign
|
||||
bin_PROGRAMS = goaccess
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
|
||||
dist_noinst_DATA = \
|
||||
resources/tpls.html \
|
||||
resources/css/app.css \
|
||||
resources/css/bootstrap.min.css \
|
||||
resources/css/fa.min.css \
|
||||
resources/js/app.js \
|
||||
resources/js/charts.js \
|
||||
resources/countries-110m.json \
|
||||
resources/cities-10m.json \
|
||||
resources/js/d3.v7.min.js \
|
||||
resources/js/topojson.v3.min.js \
|
||||
resources/js/hogan.min.js
|
||||
|
||||
noinst_PROGRAMS = bin2c
|
||||
bin2c_SOURCES = src/bin2c.c
|
||||
|
||||
BUILT_SOURCES = \
|
||||
src/tpls.h \
|
||||
src/bootstrapcss.h \
|
||||
src/facss.h \
|
||||
src/appcss.h \
|
||||
src/d3js.h \
|
||||
src/topojsonjs.h \
|
||||
src/hoganjs.h \
|
||||
src/countries110m.h \
|
||||
src/cities10m.h \
|
||||
src/chartsjs.h \
|
||||
src/appjs.h
|
||||
|
||||
CLEANFILES = \
|
||||
src/tpls.h \
|
||||
src/bootstrapcss.h \
|
||||
src/facss.h \
|
||||
src/appcss.h \
|
||||
src/d3js.h \
|
||||
src/topojsonjs.h \
|
||||
src/hoganjs.h \
|
||||
src/countries110m.h \
|
||||
src/cities10m.h \
|
||||
src/chartsjs.h \
|
||||
src/appjs.h \
|
||||
resources/tpls.html.tmp \
|
||||
resources/countries-110m.json.tmp \
|
||||
resources/cities-10m.json.tmp \
|
||||
resources/css/bootstrap.min.css.tmp \
|
||||
resources/css/fa.min.css.tmp \
|
||||
resources/css/app.css.tmp \
|
||||
resources/js/d3.v7.min.js.tmp \
|
||||
resources/js/topojson.v3.min.js.tmp \
|
||||
resources/js/hogan.min.js.tmp \
|
||||
resources/js/charts.js.tmp \
|
||||
resources/js/app.js.tmp
|
||||
|
||||
# Tpls
|
||||
src/tpls.h: bin2c$(EXEEXT) $(srcdir)/resources/tpls.html
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/tpls.html | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/tpls.html.tmp
|
||||
./bin2c $(srcdir)/resources/tpls.html.tmp src/tpls.h tpls
|
||||
else
|
||||
./bin2c $(srcdir)/resources/tpls.html src/tpls.h tpls
|
||||
endif
|
||||
# countries.json
|
||||
src/countries110m.h: bin2c$(EXEEXT) $(srcdir)/resources/countries-110m.json
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/countries-110m.json | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/countries-110m.json.tmp
|
||||
./bin2c $(srcdir)/resources/countries-110m.json.tmp src/countries110m.h countries_json
|
||||
else
|
||||
./bin2c $(srcdir)/resources/countries-110m.json src/countries110m.h countries_json
|
||||
endif
|
||||
# cities.json
|
||||
src/cities10m.h: bin2c$(EXEEXT) $(srcdir)/resources/cities-10m.json
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/cities-10m.json | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/cities-10m.json.tmp
|
||||
./bin2c $(srcdir)/resources/cities-10m.json.tmp src/cities10m.h cities_json
|
||||
else
|
||||
./bin2c $(srcdir)/resources/cities-10m.json src/cities10m.h cities_json
|
||||
endif
|
||||
# Bootstrap
|
||||
src/bootstrapcss.h: bin2c$(EXEEXT) $(srcdir)/resources/css/bootstrap.min.css
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/css/bootstrap.min.css | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/css/bootstrap.min.css.tmp
|
||||
./bin2c $(srcdir)/resources/css/bootstrap.min.css.tmp src/bootstrapcss.h bootstrap_css
|
||||
else
|
||||
./bin2c $(srcdir)/resources/css/bootstrap.min.css src/bootstrapcss.h bootstrap_css
|
||||
endif
|
||||
# Font Awesome
|
||||
src/facss.h: bin2c$(EXEEXT) $(srcdir)/resources/css/fa.min.css
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/css/fa.min.css | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/css/fa.min.css.tmp
|
||||
./bin2c $(srcdir)/resources/css/fa.min.css.tmp src/facss.h fa_css
|
||||
else
|
||||
./bin2c $(srcdir)/resources/css/fa.min.css src/facss.h fa_css
|
||||
endif
|
||||
# App.css
|
||||
src/appcss.h: bin2c$(EXEEXT) $(srcdir)/resources/css/app.css
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/css/app.css | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/css/app.css.tmp
|
||||
./bin2c $(srcdir)/resources/css/app.css.tmp src/appcss.h app_css
|
||||
else
|
||||
./bin2c $(srcdir)/resources/css/app.css src/appcss.h app_css
|
||||
endif
|
||||
# D3.js
|
||||
src/d3js.h: bin2c$(EXEEXT) $(srcdir)/resources/js/d3.v7.min.js
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/js/d3.v7.min.js | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/js/d3.v7.min.js.tmp
|
||||
./bin2c $(srcdir)/resources/js/d3.v7.min.js.tmp src/d3js.h d3_js
|
||||
else
|
||||
./bin2c $(srcdir)/resources/js/d3.v7.min.js src/d3js.h d3_js
|
||||
endif
|
||||
# topojson.js
|
||||
src/topojsonjs.h: bin2c$(EXEEXT) $(srcdir)/resources/js/topojson.v3.min.js
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/js/topojson.v3.min.js | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/js/topojson.v3.min.js.tmp
|
||||
./bin2c $(srcdir)/resources/js/topojson.v3.min.js.tmp src/topojsonjs.h topojson_js
|
||||
else
|
||||
./bin2c $(srcdir)/resources/js/topojson.v3.min.js src/topojsonjs.h topojson_js
|
||||
endif
|
||||
# Hogan.js
|
||||
src/hoganjs.h: bin2c$(EXEEXT) $(srcdir)/resources/js/hogan.min.js
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/js/hogan.min.js | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/js/hogan.min.js.tmp
|
||||
./bin2c $(srcdir)/resources/js/hogan.min.js.tmp src/hoganjs.h hogan_js
|
||||
else
|
||||
./bin2c $(srcdir)/resources/js/hogan.min.js src/hoganjs.h hogan_js
|
||||
endif
|
||||
# Charts.js
|
||||
src/chartsjs.h: bin2c$(EXEEXT) $(srcdir)/resources/js/charts.js
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/js/charts.js | sed -E "s@(,|;)[[:space:]]*//..*@\1@g" | sed -E "s@^[[:space:]]*//..*@@g" | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/js/charts.js.tmp
|
||||
./bin2c $(srcdir)/resources/js/charts.js.tmp src/chartsjs.h charts_js
|
||||
else
|
||||
./bin2c $(srcdir)/resources/js/charts.js src/chartsjs.h charts_js
|
||||
endif
|
||||
if DEBUG
|
||||
./bin2c $(srcdir)/resources/js/charts.js src/chartsjs.h charts_js
|
||||
endif
|
||||
# App.js
|
||||
src/appjs.h: bin2c$(EXEEXT) $(srcdir)/resources/js/app.js
|
||||
if HAS_SEDTR
|
||||
cat $(srcdir)/resources/js/app.js | sed -E "s@(,|;)[[:space:]]*//..*@\1@g" | sed -E "s@^[[:space:]]*//..*@@g" | sed "s/^[[:space:]]*//" | sed "/^$$/d" | tr -d "\r\n" > $(srcdir)/resources/js/app.js.tmp
|
||||
./bin2c $(srcdir)/resources/js/app.js.tmp src/appjs.h app_js
|
||||
else
|
||||
./bin2c $(srcdir)/resources/js/app.js src/appjs.h app_js
|
||||
endif
|
||||
if DEBUG
|
||||
./bin2c $(srcdir)/resources/js/app.js src/appjs.h app_js
|
||||
endif
|
||||
|
||||
confdir = $(sysconfdir)/goaccess
|
||||
dist_conf_DATA = config/goaccess.conf
|
||||
dist_conf_DATA += config/browsers.list
|
||||
dist_conf_DATA += config/podcast.list
|
||||
|
||||
goaccess_SOURCES = \
|
||||
src/base64.c \
|
||||
src/base64.h \
|
||||
src/browsers.c \
|
||||
src/browsers.h \
|
||||
src/color.c \
|
||||
src/color.h \
|
||||
src/commons.c \
|
||||
src/commons.h \
|
||||
src/csv.c \
|
||||
src/csv.h \
|
||||
src/dialogs.c \
|
||||
src/dialogs.h \
|
||||
src/error.c \
|
||||
src/error.h \
|
||||
src/fileio.c \
|
||||
src/fileio.h \
|
||||
src/gchart.c \
|
||||
src/gchart.h \
|
||||
src/gdashboard.c \
|
||||
src/gdashboard.h \
|
||||
src/gdns.c \
|
||||
src/gdns.h \
|
||||
src/gholder.c \
|
||||
src/gholder.h \
|
||||
src/gkhash.c \
|
||||
src/gkhash.h \
|
||||
src/gkmhash.c \
|
||||
src/gkmhash.h \
|
||||
src/gmenu.c \
|
||||
src/gmenu.h \
|
||||
src/goaccess.c \
|
||||
src/goaccess.h \
|
||||
src/gslist.c \
|
||||
src/gslist.h \
|
||||
src/gstorage.c \
|
||||
src/gstorage.h \
|
||||
src/gwsocket.c \
|
||||
src/gwsocket.h \
|
||||
src/json.c \
|
||||
src/json.h \
|
||||
src/khash.h \
|
||||
src/labels.h \
|
||||
src/opesys.c \
|
||||
src/opesys.h \
|
||||
src/options.c \
|
||||
src/options.h \
|
||||
src/output.c \
|
||||
src/output.h \
|
||||
src/parser.c \
|
||||
src/parser.h \
|
||||
src/persistence.c \
|
||||
src/persistence.h \
|
||||
src/pdjson.c \
|
||||
src/pdjson.h \
|
||||
src/settings.c \
|
||||
src/settings.h \
|
||||
src/sort.c \
|
||||
src/sort.h \
|
||||
src/tpl.c \
|
||||
src/tpl.h \
|
||||
src/ui.c \
|
||||
src/ui.h \
|
||||
src/util.c \
|
||||
src/util.h \
|
||||
src/websocket.c \
|
||||
src/websocket.h \
|
||||
src/xmalloc.c \
|
||||
src/xmalloc.h
|
||||
|
||||
if WITH_SSL
|
||||
goaccess_SOURCES += \
|
||||
src/wsauth.c \
|
||||
src/wsauth.h
|
||||
endif
|
||||
|
||||
if USE_SHA1
|
||||
goaccess_SOURCES += \
|
||||
src/sha1.c \
|
||||
src/sha1.h
|
||||
endif
|
||||
|
||||
if USE_MMAP
|
||||
goaccess_SOURCES += \
|
||||
src/win/mman.h \
|
||||
src/win/mmap.c
|
||||
endif
|
||||
|
||||
if GEOIP_LEGACY
|
||||
goaccess_SOURCES += \
|
||||
src/geoip1.c \
|
||||
src/geoip1.h
|
||||
endif
|
||||
|
||||
if GEOIP_MMDB
|
||||
goaccess_SOURCES += \
|
||||
src/geoip2.c \
|
||||
src/geoip1.h
|
||||
endif
|
||||
|
||||
if DEBUG
|
||||
AM_CFLAGS = -DDEBUG -O0 -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
else
|
||||
AM_CFLAGS = -O2 -DSYSCONFDIR=\"$(sysconfdir)\"
|
||||
endif
|
||||
|
||||
if WITH_RDYNAMIC
|
||||
AM_LDFLAGS = -rdynamic
|
||||
endif
|
||||
|
||||
if WITH_ZLIB
|
||||
AM_LDFLAGS = -lz
|
||||
endif
|
||||
|
||||
goaccess_LDADD = -lm
|
||||
|
||||
AM_CFLAGS += -Wall -Wextra -Wnested-externs -Wformat=2 -g
|
||||
AM_CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations
|
||||
AM_CFLAGS += -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare
|
||||
AM_CFLAGS += -Wbad-function-cast -Wcast-align
|
||||
AM_CFLAGS += -Wdeclaration-after-statement -Wshadow -Wold-style-definition
|
||||
|
||||
if WITH_ASAN
|
||||
AM_CFLAGS += -fsanitize=address
|
||||
endif
|
||||
|
||||
dist_man_MANS = goaccess.1
|
||||
|
||||
SUBDIRS = po
|
||||
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
|
||||
|
||||
EXTRA_DIST = config.rpath
|
||||
@@ -0,0 +1,148 @@
|
||||
Copyright (C) 2009-2026
|
||||
Gerardo Orellana <goaccess@prosoftcorp.com>
|
||||
|
||||
* Version history:
|
||||
- 1.10.2 [Tuesday, March 31, 2026]
|
||||
. GoAccess 1.10.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.10.1 [Saturday, Feb 14, 2026]
|
||||
. GoAccess 1.10.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.10 [Tuesday, Feb 10, 2026]
|
||||
. GoAccess 1.10 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.9.4 [Monday, Mar 31, 2025]
|
||||
. GoAccess 1.9.4 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.9.3 [Friday, May 31, 2024]
|
||||
. GoAccess 1.9.3 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.9.2 [Friday, April 12, 2024]
|
||||
. GoAccess 1.9.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.9.1 [Tuesday, February 05, 2024]
|
||||
. GoAccess 1.9.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.9 [Tuesday, January 30, 2024]
|
||||
. GoAccess 1.9 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.8.1 [Tuesday, October 31, 2023]
|
||||
. GoAccess 1.8.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.8 [Saturday, September 30, 2023]
|
||||
. GoAccess 1.8 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.7.2 [Friday, March 31, 2023]
|
||||
. GoAccess 1.7.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.7.1 [Tuesday, February 28, 2023]
|
||||
. GoAccess 1.7.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.7 [Saturday, December 31 , 2022]
|
||||
. GoAccess 1.7 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.6.5 [Monday, October 31 , 2022]
|
||||
. GoAccess 1.6.5 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.6.4 [Friday, September 30 , 2022]
|
||||
. GoAccess 1.6.4 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.6.3 [Thursday, August 31 , 2022]
|
||||
. GoAccess 1.6.3 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.6.2 [Thursday, July 14 , 2022]
|
||||
. GoAccess 1.6.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.6.1 [Thursday, June 30 , 2022]
|
||||
. GoAccess 1.6.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.6 [Tuesday, May 31 , 2022]
|
||||
. GoAccess 1.6 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.5.7 [Thursday, April 28 , 2022]
|
||||
. GoAccess 1.5.7 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.5.6 [Wednesday, March 30, 2022]
|
||||
. GoAccess 1.5.6 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.5.5 [Monday, January 31, 2022]
|
||||
. GoAccess 1.5.5 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.5.4 [Saturday, December 25, 2021]
|
||||
. GoAccess 1.5.4 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.5.3 [Thursday, November 25, 2021]
|
||||
. GoAccess 1.5.3 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.5.2 [Tuesday, Sep 28, 2021]
|
||||
. GoAccess 1.5.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.5.1 [Wednesday, Jun 30, 2021]
|
||||
. GoAccess 1.5.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.5 [Wednesday, May 26, 2021]
|
||||
. GoAccess 1.5 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.4.6 [Sunday, February 28, 2021]
|
||||
. GoAccess 1.4.6 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.4.5 [Tuesday, January 26, 2021]
|
||||
. GoAccess 1.4.5 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.4.4 [Monday, January 25, 2021]
|
||||
. GoAccess 1.4.4 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.4.3 [Friday, December 04, 2020]
|
||||
. GoAccess 1.4.3 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.4.2 [Monday, November 16, 2020]
|
||||
. GoAccess 1.4.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.4.1 [Monday, November 09, 2020]
|
||||
. GoAccess 1.4.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.4 [Monday, May 18, 2020]
|
||||
. GoAccess 1.4 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.3 [Friday, November 23, 2018]
|
||||
. GoAccess 1.3 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.2 [Tuesday, March 07, 2017]
|
||||
. GoAccess 1.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.1.1 [Wednesday, November 23, 2016]
|
||||
. GoAccess 1.1.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.1 [Tuesday, November 08, 2016]
|
||||
. GoAccess 1.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.0.2 [Tuesday, July 05, 2016]
|
||||
. GoAccess 1.0.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.0.1 [Friday, June 17, 2016]
|
||||
. GoAccess 1.0.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 1.0 [Thursday, June 09, 2016]
|
||||
. GoAccess 1.0 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.9.8 [Monday, February 29, 2016]
|
||||
. GoAccess 0.9.8 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.9.7 [Monday, December 21, 2015]
|
||||
. GoAccess 0.9.7 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.9.6 [Tuesday, October 27, 2015]
|
||||
. GoAccess 0.9.6 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.9.5 [Thursday, October 22, 2015]
|
||||
. GoAccess 0.9.5 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.9.4 [Tuesday, September 08 , 2015]
|
||||
. GoAccess 0.9.4 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.9.3 [Wednesday, August 28, 2015]
|
||||
. GoAccess 0.9.3 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.9.2 [Monday, July 06, 2015]
|
||||
. GoAccess 0.9.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.9.1 [Tuesday, May 26, 2015]
|
||||
. GoAccess 0.9.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.9 [Thursday, March 19, 2015]
|
||||
. GoAccess 0.9 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.8.5 [Sunday, September 14, 2014]
|
||||
. GoAccess 0.8.5 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.8.4 [Monday, September 08, 2014]
|
||||
. GoAccess 0.8.4 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.8.3 [Monday, July 28, 2014]
|
||||
. GoAccess 0.8.3 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.8.2 [Monday, July 21, 2014]
|
||||
. GoAccess 0.8.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.8.1 [Monday, June 16, 2014]
|
||||
. GoAccess 0.8.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.8 [Monday, May 20, 2013]
|
||||
. GoAccess 0.8 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.7.1 [Monday, February 17, 2014]
|
||||
. GoAccess 0.7.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.7 [Monday, December 16, 2013]
|
||||
. GoAccess 0.7 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.6.1 [Monday, October 07, 2013]
|
||||
. GoAccess 0.6.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.6 [Monday, July 15, 2013]
|
||||
. GoAccess 0.6 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.5 [Monday, June 04, 2012]
|
||||
. GoAccess 0.5 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.4.2 [Monday, January 03, 2011]
|
||||
. GoAccess 0.4.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.4.1 [Monday, December 13, 2010]
|
||||
. GoAccess 0.4.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.4 [Tuesday, November 30, 2010]
|
||||
. GoAccess 0.4 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.3.3 [Monday, September 27, 2010]
|
||||
. GoAccess 0.3.3 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.3.2 [Thursday, September 09 2010]
|
||||
. GoAccess 0.3.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.3.1 [Friday, September 03, 2010]
|
||||
. GoAccess 0.3.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.3 [Sunday, August 29, 2010]
|
||||
. GoAccess 0.3 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.2 [Sunday, July 25, 2010]
|
||||
. GoAccess 0.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.1.2 [Tuesday, July 13, 2010]
|
||||
. GoAccess 0.1.2 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.1.1 [Saturday, July 10, 2010]
|
||||
. GoAccess 0.1.1 Released. See ChangeLog for new features/bug-fixes.
|
||||
- 0.1 [Wednesday, July 07, 2010]
|
||||
. Welcome to the GoAccess 0.1 Released.
|
||||
@@ -0,0 +1,514 @@
|
||||
GoAccess [](https://github.com/allinurl/goaccess/actions/workflows/build-test.yml) [](https://goaccess.io)
|
||||
========
|
||||
|
||||
## What is it? ##
|
||||
GoAccess is an open source, real-time web log analyzer and interactive viewer
|
||||
that runs in a terminal on *nix systems or directly in your browser. Designed
|
||||
with system administrators, DevOps engineers, and security professionals in
|
||||
mind, it delivers fast, actionable HTTP statistics and visual server reports on
|
||||
the fly. GoAccess parses your web server logs in real time and presents the
|
||||
data directly in the terminal or via a live HTML dashboard, making it easy to
|
||||
monitor traffic, detect anomalies, and troubleshoot issues instantly.
|
||||
|
||||
More info at: [https://goaccess.io](https://goaccess.io/?src=gh).
|
||||
|
||||
[](https://goaccess.io/)
|
||||
[](https://rt.goaccess.io/?src=gh)
|
||||
|
||||
## Features ##
|
||||
GoAccess parses the specified web log file and outputs the data to the X
|
||||
terminal. Features include:
|
||||
|
||||
* **Completely Real Time**<br>
|
||||
All panels and metrics are timed to be updated every 200 ms on the terminal
|
||||
output and every second on the HTML output.
|
||||
|
||||
* **Minimal Configuration needed**<br>
|
||||
You can just run it against your access log file, pick the log format and let
|
||||
GoAccess parse the access log and show you the stats.
|
||||
|
||||
* **Track Application Response Time**<br>
|
||||
Track the time taken to serve the request. Extremely useful if you want to
|
||||
track pages that are slowing down your site.
|
||||
|
||||
* **WebSocket Authentication:**<br>
|
||||
GoAccess offers enhanced WebSocket authentication, supporting local and
|
||||
external JWT verification, with secure token refresh capabilities and seamless
|
||||
integration with external authentication systems.
|
||||
|
||||
* **Nearly All Web Log Formats**<br>
|
||||
GoAccess allows any custom log format string. Predefined options include,
|
||||
Apache, Nginx, Amazon S3, Elastic Load Balancing, CloudFront, etc.
|
||||
|
||||
* **Incremental Log Processing**<br>
|
||||
Need data persistence? GoAccess has the ability to process logs incrementally
|
||||
through the on-disk persistence options.
|
||||
|
||||
* **Only one dependency**<br>
|
||||
GoAccess is written in C. To run it, you only need ncurses as a dependency.
|
||||
That's it. It even features its own Web Socket server — http://gwsocket.io/.
|
||||
|
||||
* **Visitors**<br>
|
||||
Determine the amount of hits, visitors, bandwidth, and metrics for slowest
|
||||
running requests by the hour, or date.
|
||||
|
||||
* **Metrics per Virtual Host**<br>
|
||||
Have multiple Virtual Hosts (Server Blocks)? It features a panel that
|
||||
displays which virtual host is consuming most of the web server resources.
|
||||
|
||||
* **ASN (Autonomous System Number mapping)**<br>
|
||||
Great for detecting malicious traffic patterns and block them accordingly.
|
||||
|
||||
* **Color Scheme Customizable**<br>
|
||||
Tailor GoAccess to suit your own color taste/schemes. Either through the
|
||||
terminal, or by simply applying the stylesheet on the HTML output.
|
||||
|
||||
* **Support for Large Datasets**<br>
|
||||
GoAccess features the ability to parse large logs due to its optimized
|
||||
in-memory hash tables. It has very good memory usage and pretty good
|
||||
performance. This storage has support for on-disk persistence as well.
|
||||
|
||||
* **Docker Support**<br>
|
||||
Ability to build GoAccess' Docker image from upstream. You can still fully
|
||||
configure it, by using Volume mapping and editing `goaccess.conf`. See
|
||||
[Docker](https://github.com/allinurl/goaccess#docker) section below.
|
||||
There is also documentation how to use [docker-compose](./docker-compose/README.md).
|
||||
|
||||
### Nearly all web log formats... ###
|
||||
GoAccess allows any custom log format string. Predefined options include, but
|
||||
not limited to:
|
||||
|
||||
* Amazon CloudFront (Download Distribution).
|
||||
* Amazon Simple Storage Service (S3)
|
||||
* AWS Elastic Load Balancing
|
||||
* Combined Log Format (XLF/ELF) Apache | Nginx
|
||||
* Common Log Format (CLF) Apache
|
||||
* Google Cloud Storage.
|
||||
* Apache virtual hosts
|
||||
* Squid Native Format.
|
||||
* W3C format (IIS).
|
||||
* Caddy's JSON Structured format.
|
||||
* Traefik's CLF flavor
|
||||
|
||||
## Why GoAccess? ##
|
||||
GoAccess was designed to be a fast, terminal-based log analyzer. Its core idea
|
||||
is to quickly analyze and view web server statistics in real time without
|
||||
needing to use your browser (_great if you want to do a quick analysis of your
|
||||
access log via SSH, or if you simply love working in the terminal_).
|
||||
|
||||
It also serves as a practical tool for security monitoring, making it easy to
|
||||
spot suspicious activity, unusual traffic patterns, brute-force attempts,
|
||||
scanners, bots, and anomalous requests directly from your logs.
|
||||
|
||||
While the terminal output is the default output, it has the capability to
|
||||
generate a complete, self-contained, real-time [**`HTML`**](https://rt.goaccess.io/?src=gh)
|
||||
report, as well as a [**`JSON`**](https://goaccess.io/json?src=gh), and
|
||||
[**`CSV`**](https://goaccess.io/goaccess_csv_report.csv?src=gh) report.
|
||||
|
||||
You can see it more of a monitor command tool than anything else.
|
||||
|
||||
## Installation ##
|
||||
<a href="https://repology.org/project/goaccess/versions">
|
||||
<img src="https://repology.org/badge/vertical-allrepos/goaccess.svg" alt="Packaging status" align="right">
|
||||
</a>
|
||||
|
||||
### Build from release
|
||||
|
||||
GoAccess can be compiled and used on *nix systems.
|
||||
|
||||
Download, extract and compile GoAccess with:
|
||||
|
||||
$ wget https://tar.goaccess.io/goaccess-1.10.2.tar.gz
|
||||
$ tar -xzvf goaccess-1.10.2.tar.gz
|
||||
$ cd goaccess-1.10.2/
|
||||
$ ./configure --enable-utf8 --enable-geoip=mmdb --with-zlib
|
||||
$ make
|
||||
# make install
|
||||
|
||||
### Build from GitHub (Development) ###
|
||||
|
||||
$ git clone https://github.com/allinurl/goaccess.git
|
||||
$ cd goaccess
|
||||
$ autoreconf -fiv
|
||||
$ ./configure --enable-utf8 --enable-geoip=mmdb
|
||||
$ make
|
||||
# make install
|
||||
|
||||
### Distributions ###
|
||||
|
||||
It is easiest to install GoAccess on GNU+Linux using the preferred package manager
|
||||
of your GNU+Linux distribution. Please note that not all distributions will have
|
||||
the latest version of GoAccess available.
|
||||
|
||||
#### Debian/Ubuntu ####
|
||||
|
||||
# apt-get install goaccess
|
||||
|
||||
**Note:** It is likely this will install an outdated version of GoAccess. To
|
||||
make sure that you're running the latest stable version of GoAccess see
|
||||
alternative option below.
|
||||
|
||||
#### Official GoAccess Debian & Ubuntu repository ####
|
||||
|
||||
$ wget -O - https://deb.goaccess.io/gnugpg.key | gpg --dearmor | sudo tee /usr/share/keyrings/goaccess.gpg >/dev/null
|
||||
$ echo "deb [signed-by=/usr/share/keyrings/goaccess.gpg arch=$(dpkg --print-architecture)] https://deb.goaccess.io/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/goaccess.list
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install goaccess
|
||||
|
||||
**Note**:
|
||||
* `.deb` packages in the official repo are available through HTTPS as well. You may need to install `apt-transport-https`.
|
||||
|
||||
#### Fedora ####
|
||||
|
||||
# yum install goaccess
|
||||
|
||||
#### Arch ####
|
||||
|
||||
# pacman -S goaccess
|
||||
|
||||
#### Gentoo ####
|
||||
|
||||
# emerge net-analyzer/goaccess
|
||||
|
||||
#### OS X / Homebrew ####
|
||||
|
||||
# brew install goaccess
|
||||
|
||||
#### FreeBSD ####
|
||||
|
||||
# cd /usr/ports/sysutils/goaccess/ && make install clean
|
||||
# pkg install sysutils/goaccess
|
||||
|
||||
#### OpenBSD ####
|
||||
|
||||
# cd /usr/ports/www/goaccess && make install clean
|
||||
# pkg_add goaccess
|
||||
|
||||
#### openSUSE ####
|
||||
|
||||
# zypper ar -f obs://server:http http
|
||||
# zypper in goaccess
|
||||
|
||||
#### OpenIndiana ####
|
||||
|
||||
# pkg install goaccess
|
||||
|
||||
#### pkgsrc (NetBSD, Solaris, SmartOS, ...) ####
|
||||
|
||||
# pkgin install goaccess
|
||||
|
||||
#### Windows ####
|
||||
|
||||
GoAccess can be used in Windows through Cygwin. See Cygwin's <a
|
||||
href="https://goaccess.io/faq#installation">packages</a>. Or through the
|
||||
GNU+Linux Subsystem on Windows 10.
|
||||
|
||||
#### Docker ####
|
||||
|
||||
A Docker image has been updated, capable of directing output from an access log. If you only want to output a report, you can pipe a log from the external environment to a Docker-based process:
|
||||
|
||||
touch report.html
|
||||
cat access.log | docker run --rm -i -v ./report.html:/report.html -e LANG=$LANG allinurl/goaccess -a -o report.html --log-format COMBINED -
|
||||
|
||||
OR real-time
|
||||
|
||||
tail -F access.log | docker run -p 7890:7890 --rm -i -e LANG=$LANG allinurl/goaccess -a -o report.html --log-format COMBINED --real-time-html -
|
||||
|
||||
There is also documentation how to use [docker-compose](./docker-compose/README.md).
|
||||
|
||||
##### Build in isolated container
|
||||
|
||||
You can also build the binary for Debian based systems in an isolated container environment to prevent cluttering your local system with the development libraries:
|
||||
|
||||
$ curl -L "https://github.com/allinurl/goaccess/archive/refs/heads/master.tar.gz" | tar -xz && cd goaccess-master
|
||||
$ docker build -t goaccess/build.debian-12 -f Dockerfile.debian-12 .
|
||||
$ docker run -i --rm -v $PWD:/goaccess goaccess/build.debian-12 > goaccess
|
||||
|
||||
You can read more about using the docker image in [DOCKER.md](https://github.com/allinurl/goaccess/blob/master/DOCKER.md).
|
||||
|
||||
#### Distribution Packages ####
|
||||
|
||||
GoAccess has minimal requirements, it's written in C and requires only ncurses.
|
||||
However, below is a table of some optional dependencies in some distros to
|
||||
build GoAccess from source.
|
||||
|
||||
| Distro | NCurses | GeoIP (opt) | GeoIP2 (opt) | OpenSSL (opt) |
|
||||
| ---------------------- | ---------------- | ---------------- | --------------------- | -------------------|
|
||||
| **Ubuntu/Debian** | libncurses-dev | libgeoip-dev | libmaxminddb-dev | libssl-dev |
|
||||
| **RHEL/CentOS** | ncurses-devel | geoip-devel | libmaxminddb-devel | openssl-devel |
|
||||
| **Arch** | ncurses | geoip | libmaxminddb | openssl |
|
||||
| **Gentoo** | sys-libs/ncurses | dev-libs/geoip | dev-libs/libmaxminddb | dev-libs/openssl |
|
||||
| **Slackware** | ncurses | GeoIP | libmaxminddb | openssl |
|
||||
|
||||
**Note**: You may need to install build tools like `gcc`, `autoconf`,
|
||||
`gettext`, `autopoint` etc. for compiling/building software from source. e.g.,
|
||||
`base-devel`, `build-essential`, `"Development Tools"`.
|
||||
|
||||
## Storage ##
|
||||
|
||||
#### Default Hash Tables ####
|
||||
|
||||
In-memory storage provides better performance at the cost of limiting the
|
||||
dataset size to the amount of available physical memory. GoAccess uses
|
||||
in-memory hash tables. It has very good memory usage and pretty good
|
||||
performance. This storage has support for on-disk persistence as well.
|
||||
|
||||
## Command Line / Config Options ##
|
||||
See [**options**](https://goaccess.io/man#options) that can be supplied to the command or
|
||||
specified in the configuration file. If specified in the configuration file, long
|
||||
options need to be used without prepending `--`.
|
||||
|
||||
## Usage / Examples ##
|
||||
**Note**: Piping data into GoAccess won't prompt a log/date/time
|
||||
configuration dialog, you will need to previously define it in your
|
||||
configuration file or in the command line.
|
||||
|
||||
### Getting Started ###
|
||||
|
||||
To output to a terminal and generate an interactive report:
|
||||
|
||||
# goaccess access.log
|
||||
|
||||
To generate an HTML report:
|
||||
|
||||
# goaccess access.log -a > report.html
|
||||
|
||||
To generate a JSON report file:
|
||||
|
||||
# goaccess access.log -a -d -o report.json
|
||||
|
||||
To generate a CSV report to stdout:
|
||||
|
||||
# goaccess access.log --no-csv-summary -o csv
|
||||
|
||||
GoAccess also allows great flexibility for real-time filtering and parsing. For
|
||||
instance, to quickly diagnose issues by monitoring logs since goaccess was
|
||||
started:
|
||||
|
||||
# tail -f access.log | goaccess -
|
||||
|
||||
And even better, to filter while maintaining opened a pipe to preserve
|
||||
real-time analysis, we can make use of `tail -f` and a matching pattern tool
|
||||
such as `grep`, `awk`, `sed`, etc.:
|
||||
|
||||
# tail -f access.log | grep -i --line-buffered 'firefox' | goaccess --log-format=COMBINED -
|
||||
|
||||
or to parse from the beginning of the file while maintaining the pipe opened
|
||||
and applying a filter
|
||||
|
||||
# tail -f -n +0 access.log | grep -i --line-buffered 'firefox' | goaccess -o report.html --real-time-html -
|
||||
|
||||
|
||||
### Multiple Log files ###
|
||||
|
||||
There are several ways to parse multiple logs with GoAccess. The simplest is to
|
||||
pass multiple log files to the command line:
|
||||
|
||||
# goaccess access.log access.log.1
|
||||
|
||||
It's even possible to parse files from a pipe while reading regular files:
|
||||
|
||||
# cat access.log.2 | goaccess access.log access.log.1 -
|
||||
|
||||
**Note**: the single dash is appended to the command line to let GoAccess
|
||||
know that it should read from the pipe.
|
||||
|
||||
Now if we want to add more flexibility to GoAccess, we can use `zcat --force`
|
||||
to read compressed and uncompressed files. For instance, if we would
|
||||
like to process all log files `access.log*`, we can do:
|
||||
|
||||
# zcat --force access.log* | goaccess -
|
||||
|
||||
_Note_: On Mac OS X, use `gunzip -c` instead of `zcat`.
|
||||
|
||||
### Multi-thread Support ###
|
||||
|
||||
Use `--jobs=<count>` (or `-j`) to enable multi-thread parsing. For example:
|
||||
|
||||
# goaccess access.log -o report.html -j 4
|
||||
|
||||
|
||||
And use `--chunk-size=<256-32768>` to adjust chunk size, the default chunk size is 1024. For example:
|
||||
|
||||
# goaccess access.log -o report.html -j 4 --chunk-size=8192
|
||||
|
||||
|
||||
### Real-time HTML outputs ###
|
||||
|
||||
GoAccess has the ability the output real-time data in the HTML report. You can
|
||||
even email the HTML file since it is composed of a single file with no external
|
||||
file dependencies, how neat is that!
|
||||
|
||||
The process of generating a real-time HTML report is very similar to the
|
||||
process of creating a static report. Only `--real-time-html` is needed to make
|
||||
it real-time.
|
||||
|
||||
# goaccess access.log -o /usr/share/nginx/html/your_site/report.html --real-time-html
|
||||
|
||||
To view the report you can navigate to `http://your_site/report.html`.
|
||||
|
||||
By default, GoAccess will use the host name of the generated report.
|
||||
Optionally, you can specify the URL to which the client's browser will connect
|
||||
to. See [FAQ](https://goaccess.io/faq) for a more detailed example.
|
||||
|
||||
# goaccess access.log -o report.html --real-time-html --ws-url=goaccess.io
|
||||
|
||||
By default, GoAccess listens on port 7890, to use a different port other than
|
||||
7890, you can specify it as (make sure the port is opened):
|
||||
|
||||
# goaccess access.log -o report.html --real-time-html --port=9870
|
||||
|
||||
And to bind the WebSocket server to a different address other than 0.0.0.0, you
|
||||
can specify it as:
|
||||
|
||||
# goaccess access.log -o report.html --real-time-html --addr=127.0.0.1
|
||||
|
||||
**Note**: To output real time data over a TLS/SSL connection, you need to use
|
||||
`--ssl-cert=<cert.crt>` and `--ssl-key=<priv.key>`.
|
||||
|
||||
### Filtering ###
|
||||
|
||||
#### Working with dates ####
|
||||
|
||||
Another useful pipe would be filtering dates out of the web log
|
||||
|
||||
The following will get all HTTP requests starting on `05/Dec/2010` until the
|
||||
end of the file.
|
||||
|
||||
# sed -n '/05\/Dec\/2010/,$ p' access.log | goaccess -a -
|
||||
|
||||
or using relative dates such as yesterdays or tomorrows day:
|
||||
|
||||
# sed -n '/'$(date '+%d\/%b\/%Y' -d '1 week ago')'/,$ p' access.log | goaccess -a -
|
||||
|
||||
If we want to parse only a certain time-frame from DATE a to DATE b, we can do:
|
||||
|
||||
# sed -n '/5\/Nov\/2010/,/5\/Dec\/2010/ p' access.log | goaccess -a -
|
||||
|
||||
If we want to preserve only certain amount of data and recycle storage, we can
|
||||
keep only a certain number of days. For instance to keep & show the last 5
|
||||
days:
|
||||
|
||||
# goaccess access.log --keep-last=5
|
||||
|
||||
#### Virtual hosts ####
|
||||
|
||||
Assuming your log contains the virtual host field. For instance:
|
||||
|
||||
vhost.io:80 8.8.4.4 - - [02/Mar/2016:08:14:04 -0600] "GET /shop HTTP/1.1" 200 615 "-" "Googlebot-Image/1.0"
|
||||
|
||||
And you would like to append the virtual host to the request in order to see
|
||||
which virtual host the top urls belong to:
|
||||
|
||||
awk '$8=$1$8' access.log | goaccess -a -
|
||||
|
||||
To do the same, but also use real-time filtering and parsing:
|
||||
|
||||
tail -f access.log | unbuffer -p awk '$8=$1$8' | goaccess -a -
|
||||
|
||||
To exclude a list of virtual hosts you can do the following:
|
||||
|
||||
# grep -v "`cat exclude_vhost_list_file`" vhost_access.log | goaccess -
|
||||
|
||||
#### Files, status codes and bots ####
|
||||
|
||||
To parse specific pages, e.g., page views, `html`, `htm`, `php`, etc. within a
|
||||
request:
|
||||
|
||||
# awk '$7~/\.html|\.htm|\.php/' access.log | goaccess -
|
||||
|
||||
Note, `$7` is the request field for the common and combined log format,
|
||||
(without Virtual Host), if your log includes Virtual Host, then you probably
|
||||
want to use `$8` instead. It's best to check which field you are shooting for,
|
||||
e.g.:
|
||||
|
||||
# tail -10 access.log | awk '{print $8}'
|
||||
|
||||
Or to parse a specific status code, e.g., 500 (Internal Server Error):
|
||||
|
||||
# awk '$9~/500/' access.log | goaccess -
|
||||
|
||||
Or multiple status codes, e.g., all 3xx and 5xx:
|
||||
|
||||
# tail -f -n +0 access.log | awk '$9~/3[0-9]{2}|5[0-9]{2}/' | goaccess -o out.html -
|
||||
|
||||
And to get an estimated overview of how many bots (crawlers) are hitting your server:
|
||||
|
||||
# tail -F -n +0 access.log | grep -i --line-buffered 'bot' | goaccess -
|
||||
|
||||
### Tips ###
|
||||
|
||||
Also, it is worth pointing out that if we want to run GoAccess at lower
|
||||
priority, we can run it as:
|
||||
|
||||
# nice -n 19 goaccess -f access.log -a
|
||||
|
||||
and if you don't want to install it on your server, you can still run it from
|
||||
your local machine!
|
||||
|
||||
# ssh -n root@server 'tail -f /var/log/apache2/access.log' | goaccess - --log-format=COMBINED
|
||||
|
||||
**Note:** SSH requires `-n` so GoAccess can read from stdin. Also, make sure to
|
||||
use SSH keys for authentication as it won't work if a passphrase is required.
|
||||
|
||||
#### Troubleshooting ####
|
||||
|
||||
We receive many questions and issues that have been answered previously.
|
||||
|
||||
* Date/time matching problems? Check that your log format and the system locale in which you run GoAccess match. See [#1571](https://github.com/allinurl/goaccess/issues/1571#issuecomment-543186858)
|
||||
* Problems with pattern matching? Spaces are often a problem, see for instance [#136](https://github.com/allinurl/goaccess/issues/136), [#1579](https://github.com/allinurl/goaccess/issues/1579)
|
||||
* Other issues matching log entries: See [>200 closed issues regarding log/date/time formats](https://github.com/allinurl/goaccess/issues?q=is%3Aissue+is%3Aclosed+label%3A%22log%2Fdate%2Ftime+format%22)
|
||||
* Problems with log processing? See [>111 issues regarding log processing](https://github.com/allinurl/goaccess/issues?q=is%3Aissue+is%3Aclosed+label%3Alog-processing)
|
||||
|
||||
|
||||
#### Incremental log processing ####
|
||||
|
||||
GoAccess has the ability to process logs incrementally through its internal
|
||||
storage and dump its data to disk. It works in the following way:
|
||||
|
||||
1. A dataset must be persisted first with `--persist`, then the same dataset
|
||||
can be loaded with.
|
||||
2. `--restore`. If new data is passed (piped or through a log file), it will
|
||||
append it to the original dataset.
|
||||
|
||||
##### NOTES #####
|
||||
|
||||
GoAccess keeps track of inodes of all the files processed (assuming files will
|
||||
stay on the same partition), in addition, it extracts a snippet of data from
|
||||
the log along with the last line parsed of each file and the timestamp of the
|
||||
last line parsed. e.g., `inode:29627417|line:20012|ts:20171231235059`
|
||||
|
||||
First, it compares if the snippet matches the log being parsed, if it does, it
|
||||
assumes the log hasn't changed drastically, e.g., hasn't been truncated. If
|
||||
the inode does not match the current file, it parses all lines. If the current
|
||||
file matches the inode, it then reads the remaining lines and updates the count
|
||||
of lines parsed and the timestamp. As an extra precaution, it won't parse log
|
||||
lines with a timestamp ≤ than the one stored.
|
||||
|
||||
Piped data works based off the timestamp of the last line read. For instance,
|
||||
it will parse and discard all incoming entries until it finds a timestamp >=
|
||||
than the one stored.
|
||||
|
||||
##### Examples #####
|
||||
|
||||
// last month access log
|
||||
# goaccess access.log.1 --persist
|
||||
|
||||
then, load it with
|
||||
|
||||
// append this month access log, and preserve new data
|
||||
# goaccess access.log --restore --persist
|
||||
|
||||
To read persisted data only (without parsing new data)
|
||||
|
||||
# goaccess --restore
|
||||
|
||||
## Contributing ##
|
||||
|
||||
Any help on GoAccess is welcome. The most helpful way is to try it out and give
|
||||
feedback. Feel free to use the GitHub issue tracker and pull requests to
|
||||
discuss and submit code changes.
|
||||
|
||||
You can contribute to our translations by editing the .po files direct on GitHub or using the visual interface [inlang.com](https://inlang.com/editor/github.com/allinurl/goaccess)
|
||||
|
||||
Enjoy!
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# Security Policy
|
||||
|
||||
Found a security issue in [GoAccess](https://github.com/allinurl/goaccess)?
|
||||
Read on.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
GoAccess takes all security bugs seriously. Thank you for improving the
|
||||
security of `goaccess`. I appreciate your efforts and responsible disclosure
|
||||
and will make every effort to acknowledge your contributions.
|
||||
|
||||
Vulnerabilities should be reported to [hello@goaccess.io](mailto:hello@goaccess.io) which is a private,
|
||||
maintainer-only email address.
|
||||
|
||||
|
||||
When I receive a security bug report, I will work on the fix and prepare a
|
||||
release including the following steps:
|
||||
|
||||
* Confirm the problem and determine the affected versions.
|
||||
* Audit the code to find any potential similar problems.
|
||||
|
||||
## Comments on this Policy
|
||||
|
||||
If you have suggestions on how this process could be improved please submit a
|
||||
pull request.
|
||||
|
||||
When reporting an issue, where possible, please provide the following (if
|
||||
possible):
|
||||
|
||||
* Commit version where the issue was introduced.
|
||||
* A proof of concept (plaintext; or ideally send a patch to same email address)
|
||||
* Steps to reproduce
|
||||
* Your recommended fixes, if any.
|
||||
|
||||
> Note: Please do not open public issues for security issues, as GitHub does
|
||||
> not provide facility for private issues, and deleting the issue makes it hard
|
||||
> to triage/respond back to the reporter.
|
||||
@@ -0,0 +1,10 @@
|
||||
Copyright (C) 2009-2026
|
||||
6erardo Orellana <goaccess@prosoftcorp.com>
|
||||
|
||||
For a more comprehensive list of to-do items, please refer to the GitHub site.
|
||||
https://github.com/allinurl/goaccess/issues
|
||||
|
||||
or visit https://goaccess.io/faq#todo
|
||||
|
||||
If you are interested in working on any of the items listed in there, email
|
||||
goaccess@prosoftcorp.com or better, open a new issue:
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env sh
|
||||
# Build dynamic linked binaries on Debian.
|
||||
set -o nounset ## set -u : exit the script if you try to use an uninitialised variable
|
||||
set -o errexit ## set -e : exit the script if any statement returns a non-true return value
|
||||
|
||||
# should be either of "mmdb" or "legacy"
|
||||
geoip=${1:-"mmdb"}
|
||||
|
||||
autoreconf -fi > /dev/null 2>&1
|
||||
./configure CFLAGS=-Werror --enable-utf8 --with-openssl --with-getline --enable-geoip="$geoip" >/dev/null
|
||||
make -j > /dev/null
|
||||
cat goaccess
|
||||
@@ -0,0 +1,122 @@
|
||||
# List of browsers and their categories
|
||||
# e.g., WORD delimited by tab(s) TYPE
|
||||
# TYPE can be any type and it's not limited to the ones below.
|
||||
#
|
||||
# **IMPORTANT NOTE**:
|
||||
# ---------------------
|
||||
# The SIZE of the list is proportional to the run time.
|
||||
# Thus, the longer the list, the more time GoAccess will take to parse it.
|
||||
#
|
||||
# Also, you should note that the higher the browser/item is on the list, the
|
||||
# faster the parsing will be.
|
||||
#
|
||||
# The list needs to be specified using --browsers-file=<path>. This file is not
|
||||
# parsed by default.
|
||||
#
|
||||
# The items below are sample crawlers, adjust as needed.
|
||||
|
||||
Chef Client Crawlers
|
||||
Abonti Crawlers
|
||||
SISTRIX Crawlers
|
||||
DotBot Crawlers
|
||||
Speedy Spider Crawlers
|
||||
Sosospider Crawlers
|
||||
BPImageWalker Crawlers
|
||||
DoCoMo Crawlers
|
||||
GSLFbot Crawlers
|
||||
YodaoBot Crawlers
|
||||
AddThis Crawlers
|
||||
Purebot Crawlers
|
||||
CCBot Crawlers
|
||||
findlinks Crawlers
|
||||
ichiro Crawlers
|
||||
Linguee Bot Crawlers
|
||||
Gigabot Crawlers
|
||||
BacklinkCrawler Crawlers
|
||||
distilator Crawlers
|
||||
Aboundex Crawlers
|
||||
UnwindFetchor Crawlers
|
||||
SBIder Crawlers
|
||||
TestNutch Crawlers
|
||||
DomainCrawler Crawlers
|
||||
NextGenSearchBot Crawlers
|
||||
SEOENGWorldBot Crawlers
|
||||
Cityreview Crawlers
|
||||
PagePeeker Crawlers
|
||||
JS-Kit Crawlers
|
||||
ScreenerBot Crawlers
|
||||
ShowyouBot Crawlers
|
||||
SolomonoBot Crawlers
|
||||
Domnutch Crawlers
|
||||
MaxPoint Crawlers
|
||||
NCBot Crawlers
|
||||
TosCrawler Crawlers
|
||||
Updownerbot Crawlers
|
||||
OpenWebSpider Crawlers
|
||||
WordPress Crawlers
|
||||
PEAR Crawlers
|
||||
ZumBot Crawlers
|
||||
YisouSpider Crawlers
|
||||
W3C Crawlers
|
||||
vcheck Crawlers
|
||||
PercolateCrawler Crawlers
|
||||
NING Crawlers
|
||||
gvfs Crawlers
|
||||
CatchBot Crawlers
|
||||
Combine Crawlers
|
||||
A6-Indexer Crawlers
|
||||
Altresium Crawlers
|
||||
Comodo Crawlers
|
||||
crawler4j Crawlers
|
||||
Cricket Crawlers
|
||||
EC2LinkFinder Crawlers
|
||||
envolk Crawlers
|
||||
GeoHasher Crawlers
|
||||
HTMLParser Crawlers
|
||||
MLBot Crawlers
|
||||
Jaxified Crawlers
|
||||
LinkWalker Crawlers
|
||||
nutch Crawlers
|
||||
PostRank Crawlers
|
||||
keybase-proofs Crawlers
|
||||
CommonCrawler Crawlers
|
||||
X-CAD-SE Crawlers
|
||||
Safeassign Crawlers
|
||||
Nmap Crawlers
|
||||
sqlmap Crawlers
|
||||
Jorgee Crawlers
|
||||
PxBroker Crawlers
|
||||
Seekport Crawlers
|
||||
adscanner Crawlers
|
||||
AfD-Verbotsverfahren_JETZT! Crawlers
|
||||
DuckDuckGo-favicons-Bot Crawlers
|
||||
bingbot Crawlers
|
||||
PetalBot Crawlers
|
||||
Discordbot Crawlers
|
||||
ZoominfoBot Crawlers
|
||||
Googlebot Crawlers
|
||||
AhrefsBot Crawlers
|
||||
SemrushBot Crawlers
|
||||
Adsbot Crawlers
|
||||
BLEXBot Crawlers
|
||||
NetcraftSurveyAgent Crawlers
|
||||
Netcraft Web Server Survey Crawlers
|
||||
masscan Crawlers
|
||||
MJ12bot Crawlers
|
||||
Pandalytics Crawlers
|
||||
YandexBot Crawlers
|
||||
Nimbostratus-Bot Crawlers
|
||||
SeznamBot Crawlers
|
||||
AppleBot Crawlers
|
||||
|
||||
Vienna Feeds
|
||||
Windows-RSS-Platform Feeds
|
||||
newsbeuter Feeds
|
||||
Wrangler Feeds
|
||||
Fever Feeds
|
||||
Tiny Feeds
|
||||
FreshRSS Feeds
|
||||
KrISS Feeds
|
||||
SimplePie Feeds
|
||||
Feedsubs Feeds
|
||||
UniversalFeedParser Feeds
|
||||
@@ -0,0 +1,789 @@
|
||||
######################################
|
||||
# Time Format Options (required)
|
||||
######################################
|
||||
#
|
||||
# The hour (24-hour clock) [00,23]; leading zeros are permitted but not required.
|
||||
# The minute [00,59]; leading zeros are permitted but not required.
|
||||
# The seconds [00,60]; leading zeros are permitted but not required.
|
||||
# See `man strftime` for more details
|
||||
#
|
||||
# The following time format works with any of the
|
||||
# Apache/NGINX's log formats below.
|
||||
#
|
||||
#time-format %H:%M:%S
|
||||
#
|
||||
# Google Cloud Storage or
|
||||
# The time in microseconds since the Unix epoch.
|
||||
#
|
||||
#time-format %f
|
||||
|
||||
# Squid native log format
|
||||
#
|
||||
#time-format %s
|
||||
|
||||
######################################
|
||||
# Date Format Options (required)
|
||||
######################################
|
||||
#
|
||||
# The date-format variable followed by a space, specifies
|
||||
# the log format date containing any combination of regular
|
||||
# characters and special format specifiers. They all begin with a
|
||||
# percentage (%) sign. See `man strftime`
|
||||
#
|
||||
# The following date format works with any of the
|
||||
# Apache/NGINX's log formats below.
|
||||
#
|
||||
#date-format %d/%b/%Y
|
||||
#
|
||||
# AWS | Amazon CloudFront (Download Distribution)
|
||||
# AWS | Elastic Load Balancing
|
||||
# W3C (IIS)
|
||||
#
|
||||
#date-format %Y-%m-%d
|
||||
#
|
||||
# Google Cloud Storage or
|
||||
# The time in microseconds since the Unix epoch.
|
||||
#
|
||||
#date-format %f
|
||||
|
||||
# Squid native log format
|
||||
# Caddy
|
||||
#
|
||||
#date-format %s
|
||||
|
||||
######################################
|
||||
# Date/Time Format Option
|
||||
######################################
|
||||
#
|
||||
# The datetime-format variable followed by a space, specifies
|
||||
# the log format date and time containing any combination of regular
|
||||
# characters and special format specifiers. They all begin with a
|
||||
# percentage (%) sign. See `man strftime`
|
||||
#
|
||||
# This gives the ability to get the timezone from a request and
|
||||
# convert it to another timezone for output. See --tz=<timezone> in
|
||||
# the man page.
|
||||
#
|
||||
#datetime-format %d/%b/%Y:%H:%M:%S %z
|
||||
|
||||
######################################
|
||||
# Log Format Options (required)
|
||||
######################################
|
||||
#
|
||||
# The log-format variable followed by a space or \t for
|
||||
# tab-delimited, specifies the log format string.
|
||||
#
|
||||
# NOTE: If the time/date is a timestamp in seconds or microseconds
|
||||
# %x must be used instead of %d & %t to represent the date & time.
|
||||
|
||||
# NCSA Combined Log Format
|
||||
#log-format %h %^[%d:%t %^] "%r" %s %b "%R" "%u"
|
||||
|
||||
# NCSA Combined Log Format with Virtual Host
|
||||
#log-format %v:%^ %h %^[%d:%t %^] "%r" %s %b "%R" "%u"
|
||||
|
||||
# Common Log Format (CLF)
|
||||
#log-format %h %^[%d:%t %^] "%r" %s %b
|
||||
|
||||
# Common Log Format (CLF) with Virtual Host
|
||||
#log-format %v:%^ %h %^[%d:%t %^] "%r" %s %b
|
||||
|
||||
# W3C
|
||||
#log-format %d %t %h %^ %^ %^ %^ %r %^ %s %b %^ %^ %u %R
|
||||
|
||||
# Squid native log format
|
||||
#log-format %^ %^ %^ %v %^: %x.%^ %~%L %h %^/%s %b %m %U
|
||||
|
||||
# AWS | Amazon CloudFront (Download Distribution)
|
||||
#log-format %d\t%t\t%^\t%b\t%h\t%m\t%^\t%r\t%s\t%R\t%u\t%^
|
||||
|
||||
# Google Cloud Storage
|
||||
#log-format "%x","%h",%^,%^,"%m","%U","%s",%^,"%b","%D",%^,"%R","%u"
|
||||
|
||||
# AWS | Elastic Load Balancing
|
||||
#log-format %dT%t.%^ %^ %h:%^ %^ %T %^ %^ %^ %s %^ %b "%r" "%u"
|
||||
|
||||
# AWSS3 | Amazon Simple Storage Service (S3)
|
||||
#log-format %^[%d:%t %^] %h %^"%r" %s %^ %b %^ %L %^ "%R" "%u"
|
||||
|
||||
# Virtualmin Log Format with Virtual Host
|
||||
#log-format %h %^ %v %^[%d:%t %^] "%r" %s %b "%R" "%u"
|
||||
|
||||
# Kubernetes Nginx Ingress Log Format
|
||||
#log-format %^ %^ [%h] %^ %^ [%d:%t %^] "%r" %s %b "%R" "%u" %^ %^ [%v] %^:%^ %^ %T %^ %^
|
||||
|
||||
# CADDY JSON Structured
|
||||
#log-format {"ts":"%x.%^","request":{"client_ip":"%h","proto":"%H","method":"%m","host":"%v","uri":"%U","headers":{"User-Agent":["%u"],"Referer":["%R"]},"tls":{"cipher_suite":"%k","proto": "%K"}},"duration": "%T","size": "%b","status": "%s","resp_headers":{"Content-Type":["%M"]}}
|
||||
|
||||
# Traefik CLF flavor
|
||||
#log-format %h - %e [%d:%t %^] "%r" %s %b "%R" "%u" %^ "%v" "%U" %Lms
|
||||
|
||||
# In addition to specifying the raw log/date/time formats, for
|
||||
# simplicity, any of the following predefined log format names can be
|
||||
# supplied to the log/date/time-format variables. GoAccess can also
|
||||
# handle one predefined name in one variable and another predefined
|
||||
# name in another variable.
|
||||
#
|
||||
#log-format COMBINED
|
||||
#log-format VCOMBINED
|
||||
#log-format COMMON
|
||||
#log-format VCOMMON
|
||||
#log-format W3C
|
||||
#log-format SQUID
|
||||
#log-format CLOUDFRONT
|
||||
#log-format CLOUDSTORAGE
|
||||
#log-format AWSELB
|
||||
#log-format AWSS3
|
||||
#log-format CADDY
|
||||
#log-format TRAEFIKCLF
|
||||
|
||||
######################################
|
||||
# UI Options
|
||||
######################################
|
||||
|
||||
# Choose among color schemes
|
||||
# 1 : Monochrome
|
||||
# 2 : Green
|
||||
# 3 : Monokai (if 256-colors supported)
|
||||
#
|
||||
#color-scheme 3
|
||||
|
||||
# Prompt log/date configuration window on program start.
|
||||
#
|
||||
config-dialog false
|
||||
|
||||
# Color highlight active panel.
|
||||
#
|
||||
hl-header true
|
||||
|
||||
# Specify a custom CSS file in the HTML report.
|
||||
#
|
||||
#html-custom-css /path/file.css
|
||||
|
||||
# Specify a custom JS file in the HTML report.
|
||||
#
|
||||
#html-custom-js /path/file.js
|
||||
|
||||
# Set default HTML preferences.
|
||||
#
|
||||
# NOTE: A valid JSON object is required.
|
||||
# DO NOT USE A MULTILINE JSON OBJECT.
|
||||
# The parser will only parse the value next to `html-prefs` (single line)
|
||||
# It allows the ability to customize each panel plot. See example below.
|
||||
#
|
||||
#html-prefs {"theme":"bright","perPage":5,"layout":"horizontal","showTables":true,"visitors":{"plot":{"chartType":"bar"}}}
|
||||
|
||||
# Set HTML report page title and header.
|
||||
#
|
||||
#html-report-title My Awesome Web Stats
|
||||
|
||||
# Format JSON output using tabs and newlines.
|
||||
#
|
||||
json-pretty-print false
|
||||
|
||||
# Turn off colored output. This is the default output on
|
||||
# terminals that do not support colors.
|
||||
# true : for no color output
|
||||
# false : use color-scheme
|
||||
#
|
||||
no-color false
|
||||
|
||||
# Don't write column names in the terminal output. By default, it displays
|
||||
# column names for each available metric in every panel.
|
||||
#
|
||||
no-column-names false
|
||||
|
||||
# Disable summary metrics on the CSV output.
|
||||
#
|
||||
no-csv-summary false
|
||||
|
||||
# Disable progress metrics.
|
||||
#
|
||||
no-progress false
|
||||
|
||||
# Disable scrolling through panels on TAB.
|
||||
#
|
||||
no-tab-scroll false
|
||||
|
||||
# Disable progress metrics and parsing spinner.
|
||||
#
|
||||
#no-parsing-spinner true
|
||||
|
||||
# Do not show the last updated field displayed in the HTML generated report.
|
||||
#
|
||||
#no-html-last-updated true
|
||||
|
||||
# Outputs the report date/time data in the given timezone. Note that it
|
||||
# uses the canonical timezone name. See --datetime-format in order to
|
||||
# properly specify a timezone in the date/time format.
|
||||
#
|
||||
#tz Europe/Berlin
|
||||
|
||||
# Enable mouse support on main dashboard.
|
||||
#
|
||||
with-mouse false
|
||||
|
||||
# Maximum number of items to show per panel.
|
||||
# Note: Only the CSV and JSON outputs allow a maximum greater than the
|
||||
# default value of 366.
|
||||
#
|
||||
#max-items 366
|
||||
|
||||
# Custom colors for the terminal output
|
||||
# Tailor GoAccess to suit your own tastes.
|
||||
#
|
||||
# Color Syntax:
|
||||
# DEFINITION space/tab colorFG#:colorBG# [[attributes,] PANEL]
|
||||
#
|
||||
# FG# = foreground color number [-1...255] (-1 = default terminal color)
|
||||
# BG# = background color number [-1...255] (-1 = default terminal color)
|
||||
#
|
||||
# Optionally:
|
||||
#
|
||||
# It is possible to apply color attributes, such as:
|
||||
# bold,underline,normal,reverse,blink.
|
||||
# Multiple attributes are comma separated
|
||||
#
|
||||
# If desired, it is possible to apply custom colors per panel, that is, a
|
||||
# metric in the REQUESTS panel can be of color A, while the same metric in the
|
||||
# BROWSERS panel can be of color B.
|
||||
#
|
||||
# The following is a 256 color scheme (hybrid palette)
|
||||
#
|
||||
#color COLOR_MTRC_HITS color110:color-1
|
||||
#color COLOR_MTRC_VISITORS color173:color-1
|
||||
#color COLOR_MTRC_DATA color221:color-1
|
||||
#color COLOR_MTRC_BW color167:color-1
|
||||
#color COLOR_MTRC_AVGTS color143:color-1
|
||||
#color COLOR_MTRC_CUMTS color247:color-1
|
||||
#color COLOR_MTRC_MAXTS color186:color-1
|
||||
#color COLOR_MTRC_PROT color109:color-1
|
||||
#color COLOR_MTRC_MTHD color139:color-1
|
||||
#color COLOR_MTRC_HITS_PERC color186:color-1
|
||||
#color COLOR_MTRC_HITS_PERC_MAX color139:color-1
|
||||
#color COLOR_MTRC_HITS_PERC_MAX color139:color-1 VISITORS
|
||||
#color COLOR_MTRC_HITS_PERC_MAX color139:color-1 OS
|
||||
#color COLOR_MTRC_HITS_PERC_MAX color139:color-1 BROWSERS
|
||||
#color COLOR_MTRC_HITS_PERC_MAX color139:color-1 VISIT_TIMES
|
||||
#color COLOR_MTRC_VISITORS_PERC color186:color-1
|
||||
#color COLOR_MTRC_VISITORS_PERC_MAX color139:color-1
|
||||
#color COLOR_MTRC_BW_PERC color186:color-1
|
||||
#color COLOR_MTRC_BW_PERC_MAX color139:color-1
|
||||
#color COLOR_PANEL_COLS color243:color-1
|
||||
#color COLOR_BARS color240:color-1
|
||||
#color COLOR_SUBBARS color250:color-1
|
||||
#color COLOR_CHART_AXIS color241:color-1
|
||||
#color COLOR_CHART_VALUES color248:color-1
|
||||
#color COLOR_ERROR color231:color167
|
||||
#color COLOR_SELECTED color7:color167
|
||||
#color COLOR_PANEL_ACTIVE color7:color237
|
||||
#color COLOR_PANEL_HEADER color250:color235
|
||||
#color COLOR_PANEL_DESC color242:color-1
|
||||
#color COLOR_OVERALL_LBLS color243:color-1
|
||||
#color COLOR_OVERALL_VALS color167:color-1
|
||||
#color COLOR_OVERALL_PATH color186:color-1
|
||||
#color COLOR_ACTIVE_LABEL color139:color235 bold underline
|
||||
#color COLOR_BG color250:color-1
|
||||
#color COLOR_DEFAULT color243:color-1
|
||||
#color COLOR_PROGRESS color7:color110
|
||||
|
||||
######################################
|
||||
# Server Options
|
||||
######################################
|
||||
|
||||
# Specify IP address to bind server to.
|
||||
#
|
||||
#addr 0.0.0.0
|
||||
|
||||
# Run GoAccess as daemon (if --real-time-html enabled).
|
||||
#
|
||||
#daemonize false
|
||||
|
||||
# Ensure clients send the specified origin header upon the WebSocket
|
||||
# handshake.
|
||||
#
|
||||
#origin http://example.org
|
||||
|
||||
# The port to which the connection is being attempted to connect.
|
||||
# By default GoAccess' WebSocket server listens on port 7890
|
||||
# See man page or http://gwsocket.io for details.
|
||||
#
|
||||
#port 7890
|
||||
|
||||
# Write the PID to a file when used along the daemonize option.
|
||||
#
|
||||
#pid-file /var/run/goaccess.pid
|
||||
|
||||
# Enable real-time HTML output.
|
||||
#
|
||||
#real-time-html true
|
||||
|
||||
# Path to TLS/SSL certificate.
|
||||
# Note that ssl-cert and ssl-key need to be used to enable TLS/SSL.
|
||||
#
|
||||
#ssl-cert /path/ssl/domain.crt
|
||||
|
||||
# Path to TLS/SSL private key.
|
||||
# Note that ssl-cert and ssl-key need to be used to enable TLS/SSL.
|
||||
#
|
||||
#ssl-key /path/ssl/domain.key
|
||||
|
||||
# URL to which the WebSocket server responds. This is the URL supplied
|
||||
# to the WebSocket constructor on the client side.
|
||||
#
|
||||
# Optionally, it is possible to specify the WebSocket URI scheme, such as ws://
|
||||
# or wss:// for unencrypted and encrypted connections.
|
||||
# e.g., ws-url wss://goaccess.io
|
||||
#
|
||||
# If GoAccess is running behind a proxy, you could set the client side
|
||||
# to connect to a different port by specifying the host followed by a
|
||||
# colon and the port.
|
||||
# e.g., ws-url goaccess.io:9999
|
||||
#
|
||||
# By default, it will attempt to connect to localhost. If GoAccess is
|
||||
# running on a remote server, the host of the remote server should be
|
||||
# specified here. Also, make sure it is a valid host and NOT an http
|
||||
# address.
|
||||
#
|
||||
#ws-url goaccess.io
|
||||
|
||||
# Path to read named pipe (FIFO).
|
||||
#
|
||||
#fifo-in /tmp/wspipein.fifo
|
||||
|
||||
# Path to write named pipe (FIFO).
|
||||
#
|
||||
#fifo-out /tmp/wspipeout.fifo
|
||||
|
||||
|
||||
## WebSocket Authentication
|
||||
# See man page for more details.
|
||||
#
|
||||
# Enables WebSocket authentication using a JSON Web Token (JWT). Optionally, a
|
||||
# secret key can be provided for verification.
|
||||
#
|
||||
#ws-auth=<jwt[:secret]>
|
||||
|
||||
# Time after which the JWT expires (only for local generated JWTs).
|
||||
#
|
||||
#ws-auth-expire=<secs>
|
||||
|
||||
# URL to fetch the initial JWT. e.g., https://site.com/api/get-auth-token
|
||||
#
|
||||
#ws-auth-url=<url>
|
||||
|
||||
# URL to fetch a new JWT when initial expires. e.g.,
|
||||
# https://site.com/api/refresh-token
|
||||
#
|
||||
#ws-auth-refresh-url=<url>
|
||||
|
||||
|
||||
######################################
|
||||
# File Options
|
||||
######################################
|
||||
|
||||
# Specify the path to the input log file. If set, it will take
|
||||
# priority over -f from the command line.
|
||||
#
|
||||
#log-file /var/log/apache2/access.log
|
||||
|
||||
# Send all debug messages to the specified file.
|
||||
#
|
||||
#debug-file debug.log
|
||||
|
||||
# Specify a custom configuration file to use. If set, it will take
|
||||
# priority over the global configuration file (if any).
|
||||
#
|
||||
#config-file <filename>
|
||||
|
||||
# Log invalid requests to the specified file.
|
||||
#
|
||||
#invalid-requests <filename>
|
||||
|
||||
# Do not load the global configuration file.
|
||||
#
|
||||
#no-global-config false
|
||||
|
||||
######################################
|
||||
# Parse Options
|
||||
######################################
|
||||
|
||||
# Enable a list of user-agents by host. For faster parsing, do not
|
||||
# enable this flag.
|
||||
#
|
||||
agent-list false
|
||||
|
||||
# Enable IP resolver on HTML|JSON|CSV output.
|
||||
#
|
||||
with-output-resolver false
|
||||
|
||||
# Exclude an IPv4 or IPv6 from being counted.
|
||||
# Ranges can be included as well using a dash in between
|
||||
# the IPs (start-end).
|
||||
#
|
||||
#exclude-ip 127.0.0.1
|
||||
#exclude-ip 192.168.0.1-192.168.0.100
|
||||
#exclude-ip ::1
|
||||
#exclude-ip 0:0:0:0:0:ffff:808:804-0:0:0:0:0:ffff:808:808
|
||||
|
||||
# Include HTTP request method if found. This will create a
|
||||
# request key containing the request method + the actual request.
|
||||
#
|
||||
# <yes|no> [default: yes]
|
||||
#
|
||||
http-method yes
|
||||
|
||||
# Include HTTP request protocol if found. This will create a
|
||||
# request key containing the request protocol + the actual request.
|
||||
#
|
||||
# <yes|no> [default: yes]
|
||||
#
|
||||
http-protocol yes
|
||||
|
||||
# Write output to stdout given one of the following files and the
|
||||
# corresponding extension for the output format:
|
||||
#
|
||||
# /path/file.csv - Comma-separated values (CSV)
|
||||
# /path/file.json - JSON (JavaScript Object Notation)
|
||||
# /path/file.html - HTML
|
||||
#
|
||||
# output /path/file.html
|
||||
|
||||
# Ignore request's query string.
|
||||
# i.e., www.google.com/page.htm?query => www.google.com/page.htm
|
||||
#
|
||||
# Note: Removing the query string can greatly decrease memory
|
||||
# consumption, especially on timestamped requests.
|
||||
#
|
||||
no-query-string false
|
||||
|
||||
# Disable IP resolver on terminal output.
|
||||
#
|
||||
no-term-resolver false
|
||||
|
||||
# Treat non-standard status code 444 as 404.
|
||||
#
|
||||
444-as-404 false
|
||||
|
||||
# Add 4xx client errors to the unique visitors count.
|
||||
#
|
||||
4xx-to-unique-count false
|
||||
|
||||
# IP address anonymization
|
||||
# The IP anonymization option sets the last octet of IPv4 user IP addresses and
|
||||
# the last 80 bits of IPv6 addresses to zeros.
|
||||
# e.g., 192.168.20.100 => 192.168.20.0
|
||||
# e.g., 2a03:2880:2110:df07:face:b00c::1 => 2a03:2880:2110:df07::
|
||||
#
|
||||
#anonymize-ip false
|
||||
|
||||
# Include static files that contain a query string in the static files
|
||||
# panel.
|
||||
# e.g., /fonts/fontawesome-webfont.woff?v=4.0.3
|
||||
#
|
||||
all-static-files false
|
||||
|
||||
# Include an additional delimited list of browsers/crawlers/feeds etc.
|
||||
# See config/browsers.list for an example or
|
||||
# https://raw.githubusercontent.com/allinurl/goaccess/master/config/browsers.list
|
||||
#
|
||||
#browsers-file <filename>
|
||||
|
||||
# Date specificity. Possible values: `date` (default), or `hr` or `min`.
|
||||
#
|
||||
#date-spec hr|min
|
||||
|
||||
# Decode double-encoded values.
|
||||
#
|
||||
double-decode false
|
||||
|
||||
# Enable parsing/displaying the given panel.
|
||||
#
|
||||
#enable-panel VISITORS
|
||||
#enable-panel REQUESTS
|
||||
#enable-panel REQUESTS_STATIC
|
||||
#enable-panel NOT_FOUND
|
||||
#enable-panel HOSTS
|
||||
#enable-panel OS
|
||||
#enable-panel BROWSERS
|
||||
#enable-panel VISIT_TIMES
|
||||
#enable-panel VIRTUAL_HOSTS
|
||||
#enable-panel REFERRERS
|
||||
#enable-panel REFERRING_SITES
|
||||
#enable-panel KEYPHRASES
|
||||
#enable-panel STATUS_CODES
|
||||
#enable-panel REMOTE_USER
|
||||
#enable-panel CACHE_STATUS
|
||||
#enable-panel GEO_LOCATION
|
||||
#enable-panel ASN
|
||||
#enable-panel MIME_TYPE
|
||||
#enable-panel TLS_TYPE
|
||||
|
||||
# Hide a referrer but still count it. Wild cards are allowed. i.e., *.bing.com
|
||||
#
|
||||
#hide-referrer *.google.com
|
||||
#hide-referrer bing.com
|
||||
|
||||
# Hour specificity. Possible values: `hr` (default), or `min` (tenth
|
||||
# of a minute).
|
||||
#
|
||||
#hour-spec min
|
||||
|
||||
# Ignore crawlers from being counted.
|
||||
# This will ignore robots listed under browsers.c
|
||||
# Note that it will count them towards the total
|
||||
# number of requests, but excluded from any of the panels.
|
||||
#
|
||||
ignore-crawlers false
|
||||
|
||||
# Parse and display crawlers only.
|
||||
# This will ignore all hosts except robots listed under browsers.c
|
||||
# Note that it will count them towards the total
|
||||
# number of requests, but excluded from any of the panels.
|
||||
#
|
||||
crawlers-only false
|
||||
|
||||
# Unknown browsers and OS are considered as crawlers
|
||||
#
|
||||
unknowns-as-crawlers false
|
||||
|
||||
# Ignore static file requests.
|
||||
# req : Only ignore request from valid requests
|
||||
# panels : Ignore request from panels.
|
||||
# Note that it will count them towards the total number of requests
|
||||
# ignore-statics req
|
||||
|
||||
# Ignore parsing and displaying the given panel.
|
||||
#
|
||||
#ignore-panel VISITORS
|
||||
#ignore-panel REQUESTS
|
||||
#ignore-panel REQUESTS_STATIC
|
||||
#ignore-panel NOT_FOUND
|
||||
#ignore-panel HOSTS
|
||||
#ignore-panel OS
|
||||
#ignore-panel BROWSERS
|
||||
#ignore-panel VISIT_TIMES
|
||||
#ignore-panel VIRTUAL_HOSTS
|
||||
ignore-panel REFERRERS
|
||||
#ignore-panel REFERRING_SITES
|
||||
ignore-panel KEYPHRASES
|
||||
#ignore-panel STATUS_CODES
|
||||
#ignore-panel REMOTE_USER
|
||||
#ignore-panel CACHE_STATUS
|
||||
#ignore-panel GEO_LOCATION
|
||||
#ignore-panel ASN
|
||||
#ignore-panel MIME_TYPE
|
||||
#ignore-panel TLS_TYPE
|
||||
|
||||
# Ignore referrers from being counted.
|
||||
# This supports wild cards. For instance,
|
||||
# '*' matches 0 or more characters (including spaces)
|
||||
# '?' matches exactly one character
|
||||
#
|
||||
#ignore-referrer *.domain.com
|
||||
#ignore-referrer ww?.domain.*
|
||||
|
||||
# Ignore parsing and displaying one or multiple status code(s)
|
||||
#
|
||||
#ignore-status 400
|
||||
#ignore-status 502
|
||||
|
||||
# Keep the last specified number of days in storage. This will recycle the
|
||||
# storage tables. e.g., keep & show only the last 7 days.
|
||||
#
|
||||
# keep-last 7
|
||||
|
||||
# Disable client IP validation. Useful if IP addresses have been
|
||||
# obfuscated before being logged.
|
||||
#
|
||||
# no-ip-validation true
|
||||
|
||||
# Number of lines from the access log to test against the provided
|
||||
# log/date/time format. By default, the parser is set to test 10
|
||||
# lines. If set to 0, the parser won't test any lines and will parse
|
||||
# the whole access log.
|
||||
#
|
||||
#num-tests 10
|
||||
|
||||
# Parse log and exit without outputting data.
|
||||
#
|
||||
#process-and-exit false
|
||||
|
||||
# Display real OS names. e.g, Windows XP, Snow Leopard.
|
||||
#
|
||||
real-os true
|
||||
|
||||
# Sort panel on initial load.
|
||||
# Sort options are separated by comma.
|
||||
# Options are in the form: PANEL,METRIC,ORDER
|
||||
#
|
||||
# Available metrics:
|
||||
# BY_HITS - Sort by hits
|
||||
# BY_VISITORS - Sort by unique visitors
|
||||
# BY_DATA - Sort by data
|
||||
# BY_BW - Sort by bandwidth
|
||||
# BY_AVGTS - Sort by average time served
|
||||
# BY_CUMTS - Sort by cumulative time served
|
||||
# BY_MAXTS - Sort by maximum time served
|
||||
# BY_PROT - Sort by http protocol
|
||||
# BY_MTHD - Sort by http method
|
||||
# Available orders:
|
||||
# ASC
|
||||
# DESC
|
||||
#
|
||||
#sort-panel VISITORS,BY_DATA,ASC
|
||||
#sort-panel REQUESTS,BY_HITS,ASC
|
||||
#sort-panel REQUESTS_STATIC,BY_HITS,ASC
|
||||
#sort-panel NOT_FOUND,BY_HITS,ASC
|
||||
#sort-panel HOSTS,BY_HITS,ASC
|
||||
#sort-panel OS,BY_HITS,ASC
|
||||
#sort-panel BROWSERS,BY_HITS,ASC
|
||||
#sort-panel VISIT_TIMES,BY_DATA,DESC
|
||||
#sort-panel VIRTUAL_HOSTS,BY_HITS,ASC
|
||||
#sort-panel REFERRERS,BY_HITS,ASC
|
||||
#sort-panel REFERRING_SITES,BY_HITS,ASC
|
||||
#sort-panel KEYPHRASES,BY_HITS,ASC
|
||||
#sort-panel STATUS_CODES,BY_HITS,ASC
|
||||
#sort-panel REMOTE_USER,BY_HITS,ASC
|
||||
#sort-panel CACHE_STATUS,BY_HITS,ASC
|
||||
#sort-panel GEO_LOCATION,BY_HITS,ASC
|
||||
#sort-panel MIME_TYPE,BY_HITS,ASC
|
||||
#sort-panel TLS_TYPE,BY_HITS,ASC
|
||||
|
||||
# Consider the following extensions as static files
|
||||
# The actual '.' is required and extensions are case sensitive
|
||||
# For a full list, uncomment the less common static extensions below.
|
||||
#
|
||||
static-file .css
|
||||
static-file .js
|
||||
static-file .jpg
|
||||
static-file .png
|
||||
static-file .gif
|
||||
static-file .ico
|
||||
static-file .jpeg
|
||||
static-file .pdf
|
||||
static-file .csv
|
||||
static-file .mpeg
|
||||
static-file .mpg
|
||||
static-file .swf
|
||||
static-file .woff
|
||||
static-file .woff2
|
||||
static-file .xls
|
||||
static-file .xlsx
|
||||
static-file .doc
|
||||
static-file .docx
|
||||
static-file .ppt
|
||||
static-file .pptx
|
||||
static-file .txt
|
||||
static-file .zip
|
||||
static-file .ogg
|
||||
static-file .mp3
|
||||
static-file .mp4
|
||||
static-file .exe
|
||||
static-file .iso
|
||||
static-file .gz
|
||||
static-file .rar
|
||||
static-file .svg
|
||||
static-file .bmp
|
||||
static-file .tar
|
||||
static-file .tgz
|
||||
static-file .tiff
|
||||
static-file .tif
|
||||
static-file .ttf
|
||||
static-file .flv
|
||||
static-file .dmg
|
||||
static-file .xz
|
||||
static-file .zst
|
||||
#static-file .less
|
||||
#static-file .ac3
|
||||
#static-file .avi
|
||||
#static-file .bz2
|
||||
#static-file .class
|
||||
#static-file .cue
|
||||
#static-file .dae
|
||||
#static-file .dat
|
||||
#static-file .dts
|
||||
#static-file .ejs
|
||||
#static-file .eot
|
||||
#static-file .eps
|
||||
#static-file .img
|
||||
#static-file .jar
|
||||
#static-file .map
|
||||
#static-file .mid
|
||||
#static-file .midi
|
||||
#static-file .ogv
|
||||
#static-file .webm
|
||||
#static-file .mkv
|
||||
#static-file .odp
|
||||
#static-file .ods
|
||||
#static-file .odt
|
||||
#static-file .otf
|
||||
#static-file .pict
|
||||
#static-file .pls
|
||||
#static-file .ps
|
||||
#static-file .qt
|
||||
#static-file .rm
|
||||
#static-file .svgz
|
||||
#static-file .wav
|
||||
#static-file .webp
|
||||
|
||||
######################################
|
||||
# GeoIP Options
|
||||
# Only if configured with --enable-geoip
|
||||
######################################
|
||||
|
||||
# To feed a database either through GeoIP Legacy or GeoIP2, you need to use the
|
||||
# geoip-database flag below.
|
||||
#
|
||||
# === GeoIP Legacy
|
||||
# Legacy GeoIP has been discontinued. If your GNU+Linux distribution does not ship
|
||||
# with the legacy databases, you may still be able to find them through
|
||||
# different sources. Make sure to download the .dat files.
|
||||
#
|
||||
# Distributed with Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
# https://mailfud.org/geoip-legacy/
|
||||
|
||||
# IPv4 Country database:
|
||||
# Download the GeoIP.dat.gz
|
||||
# gunzip GeoIP.dat.gz
|
||||
#
|
||||
# IPv4 City database:
|
||||
# Download the GeoIPCity.dat.gz
|
||||
# gunzip GeoIPCity.dat.gz
|
||||
|
||||
# Standard GeoIP database for less memory usage (GeoIP Legacy).
|
||||
#
|
||||
#std-geoip false
|
||||
|
||||
# === GeoIP2
|
||||
# For GeoIP2 databases, you can use DB-IP Lite databases.
|
||||
# DB-IP is licensed under a Creative Commons Attribution 4.0 International License.
|
||||
# https://db-ip.com/db/lite.php
|
||||
|
||||
# Or you can download them from MaxMind
|
||||
# https://dev.maxmind.com/geoip/geoip2/geolite2/
|
||||
|
||||
# For GeoIP2 City database:
|
||||
# Download the GeoLite2-City.mmdb.gz
|
||||
# gunzip GeoLite2-City.mmdb.gz
|
||||
#
|
||||
# For GeoIP2 Country database:
|
||||
# Download the GeoLite2-Country.mmdb.gz
|
||||
# gunzip GeoLite2-Country.mmdb.gz
|
||||
#
|
||||
#geoip-database /usr/local/share/GeoIP/GeoLiteCity.dat
|
||||
|
||||
######################################
|
||||
# Persistence Options
|
||||
######################################
|
||||
|
||||
# Path where the persisted database files are stored on disk.
|
||||
# The default value is the /tmp directory.
|
||||
#db-path /tmp
|
||||
|
||||
# Persist parsed data into disk.
|
||||
#persist true
|
||||
|
||||
# Load previously stored data from disk.
|
||||
# Database files need to exist. See `persist`.
|
||||
#restore true
|
||||
@@ -0,0 +1,64 @@
|
||||
# vim: set noexpandtab:
|
||||
#1 This file is a user agent list of various podcast-related agents (but not
|
||||
#2 limited to). It was distilled from the user agents of a web page which hosts
|
||||
#3 a WordPress blog and a podcast.
|
||||
#4 \author Bernhard R. Fischer, <bf@abenteuerland.at>
|
||||
#5 \date 2019/09/26
|
||||
AntennaPod Podcasts
|
||||
AppleCoreMedia Mediaplayer
|
||||
atc Podcasts
|
||||
BacklinkCrawler Crawlers
|
||||
Baiduspider Crawlers
|
||||
Bullhorn Server PodCrawlers
|
||||
Castbox Podcasts
|
||||
CastBox Podcasts
|
||||
Castro Podcasts
|
||||
CCBot Crawlers
|
||||
com.evolve.podcast Podcasts
|
||||
cortex Facebook
|
||||
ExoPlayerLib Mediaplayer
|
||||
facebookexternalhit Facebook
|
||||
fyyd image poller PodCrawlers
|
||||
fyyd-poll PodCrawlers
|
||||
icatcher Podcasts
|
||||
iCatcher! Podcasts
|
||||
iTMS PodCrawlers
|
||||
itunesstored PodCrawlers
|
||||
Kodi Mediaplayer
|
||||
Lavf Mediaplayer
|
||||
LCC Crawlers
|
||||
Luminary Podcasts
|
||||
Mediatoolkitbot Crawlers
|
||||
mindUpBot Crawlers
|
||||
msnbot Crawlers
|
||||
NSPlayer Mediaplayer
|
||||
Overcast Podcasts
|
||||
Photon Crawlers
|
||||
Player FM Podcasts
|
||||
PlayerFM Podcasts
|
||||
Plex Mediaplayer
|
||||
Podbean Podcasts
|
||||
podcast Podcasts
|
||||
Podcast Podcasts
|
||||
PodcastRepublic Podcasts
|
||||
Podcasts Podcasts
|
||||
Podchaser PodCrawlers
|
||||
Podchaser-Parser PodCrawlers
|
||||
Podnews.net PodCrawlers
|
||||
PodParadise PodCrawlers
|
||||
Procast Podcasts
|
||||
ProCast Podcasts
|
||||
Procast (iOS) Podcasts
|
||||
ProcastProCast Podcasts
|
||||
radio.at PodCrawlers
|
||||
RadioPublic PodCrawlers
|
||||
RadioPublicImageResizer PodCrawlers
|
||||
RSSRadio Podcasts
|
||||
Sonos Mediaplayer
|
||||
Spotify PodCrawlers
|
||||
stagefright Mediaplayer
|
||||
Stitcher PodCrawlers
|
||||
UniversalFeedParser Feeds
|
||||
WinampMPEG Mediaplayer
|
||||
XING FeedReader Feeds
|
||||
XING-contenttabreceiver Feeds
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
# -*- Autoconf -*-
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_PREREQ([2.69])
|
||||
AC_INIT([goaccess],[1.10.2],[hello@goaccess.io],[],[https://goaccess.io])
|
||||
AM_INIT_AUTOMAKE([foreign])
|
||||
AC_CONFIG_SRCDIR([src/goaccess.c])
|
||||
AC_CONFIG_HEADERS([src/config.h])
|
||||
|
||||
# Use empty CFLAGS by default so autoconf does not add
|
||||
# CFLAGS="-O2 -g"
|
||||
# NOTE: Needs to go after AC_INIT and before AC_PROG_CC to select an
|
||||
# empty default instead.
|
||||
: ${CFLAGS=""}
|
||||
|
||||
# Prefer host default compiler
|
||||
AC_PROG_CC([cc gcc clang])
|
||||
AM_PROG_CC_C_O
|
||||
|
||||
# Check for programs
|
||||
AM_GNU_GETTEXT([external])
|
||||
AM_GNU_GETTEXT_VERSION([0.19])
|
||||
# Fix `undefined reference to `libintl_gettext'` on docker:
|
||||
AC_CHECK_LIB([intl], [libintl_dgettext])
|
||||
# Fix undefined reference to dgettext on NetBSD
|
||||
AC_CHECK_LIB([intl], [dgettext])
|
||||
|
||||
# pthread
|
||||
AC_CHECK_LIB([pthread], [pthread_create], [], [AC_MSG_ERROR([pthread is missing])])
|
||||
CFLAGS="$CFLAGS -pthread"
|
||||
|
||||
# DEBUG
|
||||
AC_ARG_ENABLE([debug],[AS_HELP_STRING([--enable-debug],[Create a debug build. Default is disabled])],[debug="$enableval"],[debug=no])
|
||||
|
||||
if test "$debug" = "yes"; then
|
||||
AC_DEFINE([_DEBUG], 1, [Debug option])
|
||||
fi
|
||||
AM_CONDITIONAL([DEBUG], [test "x$debug" = "xyes"])
|
||||
|
||||
# Handle rdynamic only on systems using GNU ld
|
||||
AC_CANONICAL_HOST
|
||||
AC_MSG_CHECKING([whether to build with rdynamic for GNU ld])
|
||||
with_rdyanimc=yes
|
||||
case "$host_os" in
|
||||
*darwin*|*cygwin*|*aix*|*mingw*) with_rdyanimc=no
|
||||
;;
|
||||
esac
|
||||
AC_MSG_RESULT([$with_rdyanimc])
|
||||
AM_CONDITIONAL([WITH_RDYNAMIC], [test "x$with_rdyanimc" = "xyes"])
|
||||
|
||||
# Add ASAN
|
||||
AC_CANONICAL_HOST
|
||||
AC_ARG_ENABLE([asan],
|
||||
[AS_HELP_STRING([--enable-asan], [Enable address sanitizer])],
|
||||
[with_asan=$enableval], [with_asan=no])
|
||||
AC_MSG_CHECKING([whether to build with address sanitizer])
|
||||
case "$host_os" in
|
||||
*cygwin*|*aix*|*mingw*) with_asan=no
|
||||
;;
|
||||
esac
|
||||
AC_MSG_RESULT([$with_asan])
|
||||
AM_CONDITIONAL([WITH_ASAN], [test "x$with_asan" = "xyes"])
|
||||
|
||||
# Check for libc implementation on NetBSD
|
||||
AC_CHECK_HEADERS([sha.h sha1.h])
|
||||
AC_CHECK_FUNCS([SHA1Init])
|
||||
AM_CONDITIONAL([USE_SHA1], [test "x$ac_cv_func_SHA1Init" != "xyes"])
|
||||
|
||||
# Build with OpenSSL
|
||||
AC_ARG_WITH([openssl],[AS_HELP_STRING([--with-openssl],[Build with OpenSSL support. Default is disabled])],[openssl="$withval"],[openssl="no"])
|
||||
|
||||
if test "$openssl" = 'yes'; then
|
||||
AC_CHECK_LIB([ssl], [SSL_CTX_new],,[AC_MSG_ERROR([ssl library missing])])
|
||||
AC_CHECK_LIB([crypto], [CRYPTO_free],,[AC_MSG_ERROR([crypto library missing])])
|
||||
AC_CHECK_LIB([ssl], [SSL_CIPHER_standard_name], [AC_DEFINE([HAVE_CIPHER_STD_NAME], 1, [HAVE_CIPHER_STD_NAME])])
|
||||
fi
|
||||
AM_CONDITIONAL([WITH_SSL], [test "x$with_openssl" = "xyes"])
|
||||
|
||||
# Build with zlib
|
||||
AC_ARG_WITH([zlib],
|
||||
[AS_HELP_STRING([--with-zlib], [Build with zlib support for reading gzipped logs. Default is disabled])],
|
||||
[zlib="$withval"],
|
||||
[zlib="no"])
|
||||
|
||||
if test "$zlib" = 'yes'; then
|
||||
AC_CHECK_LIB([z], [gzopen], [], [AC_MSG_ERROR([zlib library missing])])
|
||||
AC_CHECK_HEADERS([zlib.h], [], [AC_MSG_ERROR([zlib header missing])])
|
||||
AC_DEFINE([HAVE_ZLIB], 1, [Build with zlib support])
|
||||
fi
|
||||
AM_CONDITIONAL([WITH_ZLIB], [test "x$zlib" = "xyes"])
|
||||
|
||||
# GeoIP
|
||||
AC_ARG_ENABLE([geoip],[AS_HELP_STRING([--enable-geoip],[Enable GeoIP country lookup. Supported types: mmdb, legacy. Default is disabled])],[geoip="$enableval"],[geoip=no])
|
||||
|
||||
geolocation="N/A"
|
||||
if test "$geoip" = "mmdb"; then
|
||||
AC_CHECK_LIB([maxminddb], [MMDB_open], [], [AC_MSG_ERROR([
|
||||
*** Missing development files for libmaxminddb library.
|
||||
])])
|
||||
geolocation="GeoIP2"
|
||||
AC_DEFINE([HAVE_GEOLOCATION], 1, [Build using GeoIP.])
|
||||
elif test "$geoip" = "legacy"; then
|
||||
AC_CHECK_LIB([GeoIP], [GeoIP_new], [], [AC_MSG_ERROR([
|
||||
*** Missing development files for the GeoIP library
|
||||
])])
|
||||
geolocation="GeoIP Legacy"
|
||||
AC_DEFINE([HAVE_GEOLOCATION], 1, [Build using GeoIP.])
|
||||
elif test "$geoip" != "no"; then
|
||||
AC_MSG_ERROR([*** Invalid argument for GeoIP: $geoip])
|
||||
fi
|
||||
AM_CONDITIONAL([GEOIP_LEGACY], [test "x$geoip" = "xlegacy"])
|
||||
AM_CONDITIONAL([GEOIP_MMDB], [test "x$geoip" = "xmmdb"])
|
||||
|
||||
# GNU getline / POSIX.1-2008
|
||||
AC_ARG_WITH([getline],[AS_HELP_STRING([--with-getline],[Build using dynamic line buffer. Default is disabled])],[with_getline=$withval],[with_getline=no])
|
||||
|
||||
if test "$with_getline" = "yes"; then
|
||||
AC_DEFINE([WITH_GETLINE], 1, [Build using GNU getline.])
|
||||
fi
|
||||
|
||||
# UTF8
|
||||
AC_ARG_ENABLE([utf8],[AS_HELP_STRING([--enable-utf8],[Enable ncurses library that handles wide characters. Default is disabled])],[utf8="$enableval"],[utf8=no])
|
||||
|
||||
if test "$utf8" = "yes"; then
|
||||
libncursesw=ncursesw
|
||||
# Simply called libncurses on OS X
|
||||
case "$host_os" in
|
||||
*darwin*) libncursesw=ncurses
|
||||
;;
|
||||
esac
|
||||
|
||||
AC_CHECK_LIB([$libncursesw], [mvaddwstr], [],
|
||||
[AC_MSG_ERROR([*** Missing development libraries for ncursesw])])
|
||||
AC_SEARCH_LIBS([tputs], [tinfow], ,[AC_MSG_ERROR([Cannot find a library providing tputs])])
|
||||
AC_DEFINE([HAVE_LIBNCURSESW], [1], ["ncursesw is present."])
|
||||
|
||||
have_ncurses="yes"
|
||||
AC_CHECK_HEADERS([ncursesw/ncurses.h],[have_ncurses=yes], [], [
|
||||
#ifdef HAVE_NCURSESW_NCURSES_H
|
||||
#include <ncursesw/ncurses.h>
|
||||
#endif
|
||||
])
|
||||
|
||||
AC_CHECK_HEADERS([ncurses.h],[have_ncurses=yes], [], [
|
||||
#ifdef HAVE_NCURSES_H
|
||||
#include <ncurses.h>
|
||||
#endif
|
||||
])
|
||||
|
||||
if test "$have_ncurses" != "yes"; then
|
||||
AC_MSG_ERROR([Missing ncursesw header file])
|
||||
fi
|
||||
else
|
||||
AC_CHECK_LIB([ncurses], [refresh], [],
|
||||
[AC_CHECK_LIB([curses], [refresh], [],
|
||||
[AC_MSG_ERROR([*** Missing development libraries for ncurses])])])
|
||||
AC_SEARCH_LIBS([tputs], [tinfo], ,[AC_MSG_ERROR([Cannot find a library providing tputs])])
|
||||
|
||||
have_ncurses="yes"
|
||||
AC_CHECK_HEADERS([ncurses/ncurses.h],[have_ncurses=yes], [], [
|
||||
#ifdef HAVE_NCURSES_NCURSES_H
|
||||
#include <ncurses/ncurses.h>
|
||||
#endif
|
||||
])
|
||||
|
||||
AC_CHECK_HEADERS([ncurses.h],[have_ncurses=yes], [], [
|
||||
#ifdef HAVE_NCURSES_H
|
||||
#include <ncurses.h>
|
||||
#endif
|
||||
])
|
||||
|
||||
AC_CHECK_HEADERS([curses.h],[have_ncurses=yes], [], [
|
||||
#ifdef HAVE_CURSES_H
|
||||
#include <curses.h>
|
||||
#endif
|
||||
])
|
||||
|
||||
if test "$have_ncurses" != "yes"; then
|
||||
AC_MSG_ERROR([Missing ncurses header file])
|
||||
fi
|
||||
fi
|
||||
|
||||
# Default Hash
|
||||
storage="In-Memory with On-Disk Persistent Storage"
|
||||
|
||||
HAS_SEDTR=no
|
||||
AC_CHECK_PROG([SED_CHECK],[sed],[yes],[no])
|
||||
if test x"$SED_CHECK" = x"yes" ; then
|
||||
AC_CHECK_PROG([TR_CHECK],[tr],[yes],[no])
|
||||
if test x"$TR_CHECK" = x"yes" ; then
|
||||
HAS_SEDTR=yes
|
||||
fi
|
||||
fi
|
||||
AM_CONDITIONAL([HAS_SEDTR], [test "x$HAS_SEDTR" = xyes])
|
||||
|
||||
# detect Cygwin or MinGW and use mmap family replacements
|
||||
USE_MMAP=no
|
||||
case $host in
|
||||
*-*-mingw32* | *-*-cygwin* | *-*-windows*)
|
||||
USE_MMAP=yes
|
||||
AC_MSG_NOTICE([using custom mmap for Cygwin/MinGW])
|
||||
;;
|
||||
esac
|
||||
AM_CONDITIONAL([USE_MMAP], [test "x$USE_MMAP" = xyes])
|
||||
|
||||
# Solaris
|
||||
AC_CHECK_LIB([nsl], [gethostbyname])
|
||||
AC_CHECK_LIB([socket], [socket])
|
||||
|
||||
# Checks for header files.
|
||||
AC_HEADER_STDC
|
||||
AC_CHECK_HEADERS([arpa/inet.h])
|
||||
AC_CHECK_HEADERS([fcntl.h])
|
||||
AC_CHECK_HEADERS([inttypes.h])
|
||||
AC_CHECK_HEADERS([limits.h])
|
||||
AC_CHECK_HEADERS([locale.h])
|
||||
AC_CHECK_HEADERS([netdb.h])
|
||||
AC_CHECK_HEADERS([netinet/in.h])
|
||||
AC_CHECK_HEADERS([stddef.h])
|
||||
AC_CHECK_HEADERS([stdint.h])
|
||||
AC_CHECK_HEADERS([stdlib.h])
|
||||
AC_CHECK_HEADERS([string.h])
|
||||
AC_CHECK_HEADERS([strings.h])
|
||||
AC_CHECK_HEADERS([sys/socket.h])
|
||||
AC_CHECK_HEADERS([sys/time.h])
|
||||
AC_CHECK_HEADERS([unistd.h])
|
||||
|
||||
# Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_C_CONST
|
||||
AC_CHECK_TYPES([ptrdiff_t])
|
||||
AC_STRUCT_TM
|
||||
AC_TYPE_INT64_T
|
||||
AC_TYPE_INT8_T
|
||||
AC_TYPE_OFF_T
|
||||
AC_TYPE_SIZE_T
|
||||
AC_TYPE_UINT32_T
|
||||
AC_TYPE_UINT64_T
|
||||
AC_TYPE_UINT8_T
|
||||
|
||||
# Checks for library functions.
|
||||
AC_FUNC_FSEEKO
|
||||
AC_FUNC_MEMCMP
|
||||
AC_FUNC_MKTIME
|
||||
AC_FUNC_STAT
|
||||
AC_FUNC_STRFTIME
|
||||
AC_FUNC_STRTOD
|
||||
AC_CHECK_FUNCS([floor])
|
||||
AC_CHECK_FUNCS([gethostbyaddr])
|
||||
AC_CHECK_FUNCS([gethostbyname])
|
||||
AC_CHECK_FUNCS([gettimeofday])
|
||||
AC_CHECK_FUNCS([malloc])
|
||||
AC_CHECK_FUNCS([memmove])
|
||||
AC_CHECK_FUNCS([memset])
|
||||
AC_CHECK_FUNCS([mkfifo])
|
||||
AC_CHECK_FUNCS([poll])
|
||||
AC_CHECK_FUNCS([realloc])
|
||||
AC_CHECK_FUNCS([realpath])
|
||||
AC_CHECK_FUNCS([regcomp])
|
||||
AC_CHECK_FUNCS([setlocale])
|
||||
AC_CHECK_FUNCS([socket])
|
||||
AC_CHECK_FUNCS([strcasecmp])
|
||||
AC_CHECK_FUNCS([strchr])
|
||||
AC_CHECK_FUNCS([strcspn])
|
||||
AC_CHECK_FUNCS([strdup])
|
||||
AC_CHECK_FUNCS([strerror])
|
||||
AC_CHECK_FUNCS([strncasecmp])
|
||||
AC_CHECK_FUNCS([strpbrk])
|
||||
AC_CHECK_FUNCS([strrchr])
|
||||
AC_CHECK_FUNCS([strspn])
|
||||
AC_CHECK_FUNCS([strstr])
|
||||
AC_CHECK_FUNCS([strtol])
|
||||
AC_CHECK_FUNCS([strtoull])
|
||||
AC_CHECK_FUNCS([timegm])
|
||||
|
||||
AC_CONFIG_FILES([Makefile po/Makefile.in])
|
||||
AC_OUTPUT
|
||||
|
||||
cat << EOF
|
||||
|
||||
Your build configuration:
|
||||
|
||||
Prefix : $prefix
|
||||
Package : $PACKAGE_NAME
|
||||
Version : $VERSION
|
||||
Compiler flags : $CFLAGS
|
||||
Linker flags : $LIBS $LDFLAGS
|
||||
UTF-8 support : $utf8
|
||||
Dynamic buffer : $with_getline
|
||||
ASan : $with_asan
|
||||
Geolocation : $geolocation
|
||||
Storage method : $storage
|
||||
TLS/SSL : $openssl
|
||||
zlib support : $zlib
|
||||
Bugs : $PACKAGE_BUGREPORT
|
||||
|
||||
EOF
|
||||
@@ -0,0 +1,34 @@
|
||||
# Docker-compose configuration
|
||||
|
||||
Here are two docker-compose configurations for goaccess which
|
||||
combine a static site with a real-time report of goaccess.
|
||||
The directories used are:
|
||||
|
||||
- `configs` - for the nginx and goaccess configuration
|
||||
- `public` - the files served by nginx - put your static site here
|
||||
- `logs` - nginx logs - there is no log rotation in place!
|
||||
|
||||
There are two flavors of the docker-compose files and the goaccess files:
|
||||
- `*.vanilla.*` which need you to take care of the TLS certificates
|
||||
- `*.traefik.*` using traefik for TLS and domain routing
|
||||
|
||||
## Vanilla
|
||||
|
||||
For the vanilla version, you'll have to do the following:
|
||||
|
||||
- put the TLS certificates into `configs/certs`, following the naming scheme
|
||||
in `goaccess.vanilla.conf`
|
||||
- route requests for the static webpage to port `8080`
|
||||
|
||||
## Traefik
|
||||
|
||||
The traefik version is setup to make it easier to do the routing.
|
||||
You don't need to take care of the TLS certificates, and the
|
||||
goaccess websocket gets its own subdomain.
|
||||
|
||||
To put it all together, the following environment is needed:
|
||||
- traefik configured according to [Traefik-101](https://ineiti.ch/posts/traefik-101/traefik-101/)
|
||||
- DNS configuration for two domain names pointing to your server's IP:
|
||||
- `yourdomain` for the static pages, e.g., a blog-post using [Hugo](https://gohugo.io/)
|
||||
- `goaccess.yourdomain` for the stats with goaccess
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
tz Europe/Zurich
|
||||
time-format %H:%M:%S
|
||||
date-format %Y-%m-%d
|
||||
log-format COMBINED
|
||||
log-file /srv/logs/access.log
|
||||
output /srv/report/report.html
|
||||
real-time-html true
|
||||
ws-url wss://goaccess.yourdomain
|
||||
port 443
|
||||
@@ -0,0 +1,10 @@
|
||||
tz Europe/Zurich
|
||||
time-format %H:%M:%S
|
||||
date-format %Y-%m-%d
|
||||
log-format COMBINED
|
||||
log-file /srv/logs/access.log
|
||||
output /srv/report/report.html
|
||||
real-time-html true
|
||||
ws-url wss://yourdomain
|
||||
ssl-cert /srv/certs/yourdomain.crt
|
||||
ssl-key /srv/certs/yourdomain.key
|
||||
@@ -0,0 +1,31 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log notice;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
#gzip on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
services:
|
||||
web:
|
||||
image: nginx
|
||||
volumes:
|
||||
- ./logs:/var/log/nginx
|
||||
- ./configs/nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./public:/usr/share/nginx/html
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "fqdn=yourdomain"
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
goaccess:
|
||||
image: allinurl/goaccess
|
||||
container_name: goaccess
|
||||
volumes:
|
||||
- ./configs/goaccess.traefik.conf:/srv/config/goaccess.conf
|
||||
- ./logs:/srv/logs
|
||||
- ./public:/srv/report
|
||||
command: ["--no-global-config", "--config-file=/srv/config/goaccess.conf"]
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "fqdn=goaccess.yourdomain"
|
||||
- "traefik.http.services.yourdomain-goaccess.loadbalancer.server.port=443"
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
external:
|
||||
name: traefik_traefik
|
||||
@@ -0,0 +1,21 @@
|
||||
services:
|
||||
web:
|
||||
image: nginx
|
||||
ports:
|
||||
- 8080:80
|
||||
volumes:
|
||||
- ./logs:/var/log/nginx
|
||||
- ./configs/nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./public:/usr/share/nginx/html
|
||||
|
||||
goaccess:
|
||||
image: allinurl/goaccess
|
||||
ports:
|
||||
- 7890:7890
|
||||
volumes:
|
||||
- ./configs/goaccess.vanilla.conf:/srv/config/goaccess.conf
|
||||
- ./logs:/srv/logs
|
||||
- ./public:/srv/report
|
||||
- ./configs/certs:/srv/certs
|
||||
command: ["--no-global-config", "--config-file=/srv/config/goaccess.conf"]
|
||||
restart: unless-stopped
|
||||
+1540
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
// init the inlang.config
|
||||
|
||||
/**
|
||||
* @type {import("@inlang/core/config").DefineConfig}
|
||||
*/
|
||||
export async function defineConfig(env) {
|
||||
// importing plugin from local file for testing purposes
|
||||
const plugin = await env.$import(
|
||||
"https://cdn.jsdelivr.net/gh/jannesblobel/inlang-plugin-po@1/dist/index.js"
|
||||
);
|
||||
const pluginConfig = {
|
||||
// language mean the name of you file
|
||||
pathPattern: "./po/{language}.po",
|
||||
referenceResourcePath: "./po/goaccess.pot",
|
||||
};
|
||||
|
||||
return {
|
||||
// if your project use a pot file use the pot as the reference Language
|
||||
// !! do not add the pot file in the Languages array
|
||||
/**
|
||||
* @example
|
||||
* example files: en.pot, de.po, es.po, fr.po
|
||||
* referenceLanguage: "en",
|
||||
languages: ["de","es","fr"],
|
||||
*/
|
||||
referenceLanguage: "en",
|
||||
languages: await getLanguages(env),
|
||||
readResources: (args) =>
|
||||
plugin.readResources({ ...args, ...env, pluginConfig }),
|
||||
writeResources: (args) =>
|
||||
plugin.writeResources({ ...args, ...env, pluginConfig }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically derives the languages in this repository.
|
||||
*/
|
||||
async function getLanguages(env) {
|
||||
const files = await env.$fs.readdir("./po");
|
||||
// files that end with .json
|
||||
// remove the .json extension to only get language name
|
||||
const languages = files
|
||||
.filter((name) => name.endsWith(".po"))
|
||||
.map((name) => name.replace(".po", ""));
|
||||
return languages;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
de
|
||||
es
|
||||
fr
|
||||
ja
|
||||
ka
|
||||
ko
|
||||
sv
|
||||
zh_CN
|
||||
pt_BR
|
||||
ru
|
||||
uk
|
||||
it
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
# Makefile variables for PO directory in any package using GNU gettext.
|
||||
|
||||
# Usually the message domain is the same as the package name.
|
||||
DOMAIN = $(PACKAGE)
|
||||
|
||||
# These two variables depend on the location of this directory.
|
||||
subdir = po
|
||||
top_builddir = ..
|
||||
AM_CPPFLAGS = -I. -I$(srcdir)
|
||||
|
||||
# These options get passed to xgettext.
|
||||
XGETTEXT_OPTIONS = --from-code=UTF-8 --keyword=_ --keyword=N_
|
||||
|
||||
# This is the copyright holder that gets inserted into the header of the
|
||||
# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding
|
||||
# package. (Note that the msgstr strings, extracted from the package's
|
||||
# sources, belong to the copyright holder of the package.) Translators are
|
||||
# expected to transfer the copyright for their translations to this person
|
||||
# or entity, or to disclaim their copyright. The empty string stands for
|
||||
# the public domain; in this case the translators are expected to disclaim
|
||||
# their copyright.
|
||||
COPYRIGHT_HOLDER = Free Software Foundation, Inc.
|
||||
|
||||
# This tells whether or not to prepend "GNU " prefix to the package
|
||||
# name that gets inserted into the header of the $(DOMAIN).pot file.
|
||||
# Possible values are "yes", "no", or empty. If it is empty, try to
|
||||
# detect it automatically by scanning the files in $(top_srcdir) for
|
||||
# "GNU packagename" string.
|
||||
PACKAGE_GNU =
|
||||
|
||||
# This is the email address or URL to which the translators shall report
|
||||
# bugs in the untranslated strings:
|
||||
# - Strings which are not entire sentences, see the maintainer guidelines
|
||||
# in the GNU gettext documentation, section 'Preparing Strings'.
|
||||
# - Strings which use unclear terms or require additional context to be
|
||||
# understood.
|
||||
# - Strings which make invalid assumptions about notation of date, time or
|
||||
# money.
|
||||
# - Pluralisation problems.
|
||||
# - Incorrect English spelling.
|
||||
# - Incorrect formatting.
|
||||
# It can be your email address, or a mailing list address where translators
|
||||
# can write to without being subscribed, or the URL of a web page through
|
||||
# which the translators can contact you.
|
||||
MSGID_BUGS_ADDRESS = hello@goaccess.io
|
||||
|
||||
# This is the list of locale categories, beyond LC_MESSAGES, for which the
|
||||
# message catalogs shall be used. It is usually empty.
|
||||
EXTRA_LOCALE_CATEGORIES =
|
||||
|
||||
# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'
|
||||
# context. Possible values are "yes" and "no". Set this to yes if the
|
||||
# package uses functions taking also a message context, like pgettext(), or
|
||||
# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.
|
||||
USE_MSGCTXT = no
|
||||
|
||||
# These options get passed to msgmerge.
|
||||
# Useful options are in particular:
|
||||
# --previous to keep previous msgids of translated messages,
|
||||
# --quiet to reduce the verbosity.
|
||||
MSGMERGE_OPTIONS =
|
||||
|
||||
# These options get passed to msginit.
|
||||
# If you want to disable line wrapping when writing PO files, add
|
||||
# --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and
|
||||
# MSGINIT_OPTIONS.
|
||||
MSGINIT_OPTIONS =
|
||||
|
||||
# This tells whether or not to regenerate a PO file when $(DOMAIN).pot
|
||||
# has changed. Possible values are "yes" and "no". Set this to no if
|
||||
# the POT file is checked in the repository and the version control
|
||||
# program ignores timestamps.
|
||||
PO_DEPENDS_ON_POT = yes
|
||||
|
||||
# This tells whether or not to forcibly update $(DOMAIN).pot and
|
||||
# regenerate PO files on "make dist". Possible values are "yes" and
|
||||
# "no". Set this to no if the POT file and PO files are maintained
|
||||
# externally.
|
||||
DIST_DEPENDS_ON_UPDATE_PO = yes
|
||||
@@ -0,0 +1,2 @@
|
||||
# List of source files which contain translatable strings.
|
||||
src/labels.h
|
||||
+1080
File diff suppressed because it is too large
Load Diff
+1111
File diff suppressed because it is too large
Load Diff
+1078
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
Vendored
+6
File diff suppressed because one or more lines are too long
Vendored
+407
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path fill="#1b1b1b" d="M0 16V0h32v32H0Z"/>
|
||||
<path fill="#afd42a" d="M8.075 20.932V19.92h4.05v2.025h-4.05Zm0 4.05V23.97h4.05v2.026h-4.05Zm0 4.05v-1.013h4.05v2.025h-4.05Z"/>
|
||||
<path fill="#fb1161" d="M2 20.932V19.92h4.05v2.025H2Zm0 4.05V23.97h4.05v2.026H2Zm0 4.05v-1.013h4.05v2.025H2Z"/>
|
||||
<path fill="#585858" d="M0 16.2v-2.025h32v4.05H0ZM0 2.024V0h32v4.05H0Z"/>
|
||||
<path fill="#65d7fd" d="M14.27 29.057v-.943h3.905V30H14.27zm0-8.08v-.942h3.905v1.885H14.27zm0 4.04v-.942h3.905v1.885H14.27z"/>
|
||||
<path fill="#c6c6c6" d="M20.33 29.057v-.943h3.904V30H20.33zm0-4.04v-.942h7.944v1.885H20.33zm0-4.04v-.942h5.924v1.885H20.33z"/>
|
||||
<path fill="#afd42a" d="M10.1 7.013V6h8.1v2.025h-8.1z"/>
|
||||
<path fill="#c6c6c6" d="M2.016 7.016v-1.01h6.06v2.02h-6.06z"/>
|
||||
<path fill="#afd42a" d="M10.1 11.063V10.05h8.1v2.025h-8.1z"/>
|
||||
<path fill="#c6c6c6" d="M2.016 11.066v-1.01h6.06v2.02h-6.06z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 946 B |
+2445
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Vendored
+2
File diff suppressed because one or more lines are too long
Vendored
+5
File diff suppressed because one or more lines are too long
Vendored
+2
File diff suppressed because one or more lines are too long
@@ -0,0 +1,336 @@
|
||||
<!-- TPL General -->
|
||||
<script id="tpl-general" type="text/template">
|
||||
<hgroup>
|
||||
<h2 class="gheader h4" id="overall-heading">{{head}}</h2>
|
||||
<p>{{#from}}{{from}}{{/from}}{{#to}} – {{to}}{{/to}}</p>
|
||||
</hgroup>
|
||||
<ul role="list" class="row"></ul>
|
||||
</script>
|
||||
|
||||
<!-- TPL General Items -->
|
||||
<script id="tpl-general-items" type="text/template">
|
||||
<li class="col-sm-6 col-md-2 {{#className}}{{className}}{{/className}}{{^className}}gray{{/className}}" aria-live="polite" aria-atomic="true">
|
||||
<div>
|
||||
<span><i class="fa fa-bar-chart"></i> {{#label}}{{label}}{{/label}}</span>
|
||||
<span id="{{id}}">{{value}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</script>
|
||||
|
||||
<!-- TPL Panel Table -->
|
||||
<script id="tpl-table-row" type="text/template">
|
||||
{{#rows}}
|
||||
<tr class="{{#className}}{{className}} {{/className}}{{#hasSubItems}}{{#items}}expandable{{/items}}{{/hasSubItems}} level-{{level}}" data-panel="{{panel}}" {{#key}}data-key="{{key}}" {{/key}} {{#nodeKey}}data-node-key="{{nodeKey}}" {{/nodeKey}} data-level="{{level}}">
|
||||
{{#hasSubItems}}
|
||||
<td class="row-expandable text-center {{#items}}clickable{{/items}}">
|
||||
{{#items}}<i class="fa {{#expanded}}fa-caret-down{{/expanded}}{{^expanded}}fa fa-caret-right{{/expanded}}"></i>{{/items}}
|
||||
{{^items}}<i></i>{{/items}}
|
||||
</td>
|
||||
{{/hasSubItems}}
|
||||
{{^hasSubItems}}
|
||||
{{#showPlaceholder}}
|
||||
<td class="row-expandable text-center">
|
||||
<i></i>
|
||||
</td>
|
||||
{{/showPlaceholder}}
|
||||
{{/hasSubItems}}
|
||||
<td class="row-idx text-right">
|
||||
{{#idx}}{{idx}}{{/idx}}
|
||||
</td>
|
||||
{{#cells}}
|
||||
<td class="{{className}}" {{#colspan}}colspan="{{colspan}}"{{/colspan}}>
|
||||
<span class="value">{{{value}}}</span>{{#percent}}<span class="percent"> ({{percent}})</span>{{/percent}}
|
||||
</td>
|
||||
{{/cells}}
|
||||
</tr>
|
||||
{{/rows}}
|
||||
</script>
|
||||
|
||||
<!-- TPL Panel Table Meta -->
|
||||
<script id="tpl-table-row-meta" type="text/template">
|
||||
{{#row}}
|
||||
{{#hasSubItems}}
|
||||
<th class=""></th>
|
||||
{{/hasSubItems}}
|
||||
<th class="meta-label">{{key}}.</th>
|
||||
{{#cells}}
|
||||
<th class="{{className}}" {{#colspan}}colspan="{{colspan}}"{{/colspan}}>
|
||||
{{#value}}
|
||||
<span>{{value}} {{#percent}}<span class="text-muted">({{percent}}%)</span>{{/percent}}</span>
|
||||
{{/value}}
|
||||
{{^value}}
|
||||
<span class="text-muted">—</span>
|
||||
{{/value}}
|
||||
</th>
|
||||
{{/cells}}
|
||||
{{/row}}
|
||||
</script>
|
||||
|
||||
<!-- TPL Panel Table Totals -->
|
||||
<script id="tpl-table-row-totals" type="text/template">
|
||||
{{#row}}
|
||||
{{#hasSubItems}}
|
||||
<td class=""></td>
|
||||
{{/hasSubItems}}
|
||||
<td class="meta-label">Tot.</td>
|
||||
{{#cells}}
|
||||
<td class="{{className}}" {{#colspan}}colspan="{{colspan}}"{{/colspan}}>
|
||||
{{#label}}
|
||||
<span>{{label}}</span>
|
||||
{{/label}}
|
||||
<span>{{value}}</span>
|
||||
</td>
|
||||
{{/cells}}
|
||||
{{/row}}
|
||||
</script>
|
||||
|
||||
<!-- TPL Table thead -->
|
||||
<script id="tpl-table-thead" type="text/template">
|
||||
{{#hasSubItems}}
|
||||
<th></th>
|
||||
{{/hasSubItems}}
|
||||
<th>#</th>
|
||||
{{#items}}
|
||||
<th class="{{dataType}} {{#key}}sortable{{/key}}" data-key="{{key}}" {{#sort}}data-order="{{#asc}}asc{{/asc}}{{^asc}}desc{{/asc}}"{{/sort}}>
|
||||
{{label}} <i class="fa fa-{{^sort}}sort{{/sort}}{{#sort}}{{#asc}}caret-up{{/asc}}{{^asc}}caret-down{{/asc}}{{/sort}}"></i>
|
||||
</th>
|
||||
{{/items}}
|
||||
</script>
|
||||
|
||||
<!-- TPL Panel Options DropDown -->
|
||||
<script id="tpl-panel-opts" type="text/template">
|
||||
<li class="dropdown-header">{{ labels.chart_opts }}</li>
|
||||
<li><a href="javascript:void(0);" data-panel="{{id}}" data-chart="{{showChart}}"><i class="fa fa-{{#showChart}}check-{{/showChart}}square-o"></i> {{labels.chart}}</a></li>
|
||||
|
||||
{{#plot.length}}
|
||||
<li class="dropdown-header">{{ labels.type }}</li>
|
||||
<li><a href="javascript:void(0);" data-panel="{{id}}" data-chart-type="area-spline"><i class="fa fa-circle{{^area-spline}}-o{{/area-spline}}"></i> {{labels.area_spline}}</a></li>
|
||||
<li><a href="javascript:void(0);" data-panel="{{id}}" data-chart-type="bar"><i class="fa fa-circle{{^bar}}-o{{/bar}}"></i> {{labels.bar}}</a></li>
|
||||
{{#hasMap}}
|
||||
<li><a href="javascript:void(0);" data-panel="{{id}}" data-chart-type="wmap"><i class="fa fa-circle{{^wmap}}-o{{/wmap}}"></i> {{labels.wmap}}</a></li>
|
||||
<li><a href="javascript:void(0);" data-panel="{{id}}" data-chart-type="gmap"><i class="fa fa-circle{{^gmap}}-o{{/gmap}}"></i> {{labels.gmap}}</a></li>
|
||||
{{/hasMap}}
|
||||
<li class="dropdown-header">{{labels.plot_metric}}</li>
|
||||
{{#plot}}
|
||||
<li><a href="javascript:void(0);" data-panel="{{id}}" data-plot="{{className}}" class="panel-plot-{{className}}"><i class="fa fa-circle{{^selected}}-o{{/selected}}"></i> {{label}}</a></li>
|
||||
{{/plot}}
|
||||
{{/plot.length}}
|
||||
|
||||
<li class="dropdown-header">{{labels.table_columns}}</li>
|
||||
{{#items}}
|
||||
<li><a href="javascript:void(0);" data-panel="{{id}}" data-metric="{{key}}"><i class="fa fa-{{^hide}}check-{{/hide}}square-o"></i> {{label}}</a></li>
|
||||
{{/items}}
|
||||
</script>
|
||||
|
||||
<!-- TPL Table colgroup -->
|
||||
<script id="tpl-table-colgroup" type="text/template">
|
||||
{{#hasSubItems}}
|
||||
<col style="width: 2%;"> <!-- right-caret -->
|
||||
{{/hasSubItems}}
|
||||
<col style="width: 3%;"> <!-- row # -->
|
||||
{{#items}}
|
||||
<col style="width:{{colWidth}}">
|
||||
{{/items}}
|
||||
</script>
|
||||
|
||||
<!-- TPL Panel -->
|
||||
<script id="tpl-panel" type="text/template">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<header>
|
||||
<hgroup>
|
||||
<h2 id="{{id}}" class="gheader">{{head}}</h2>
|
||||
<small class="text-muted">{{desc}}</small>
|
||||
</hgroup>
|
||||
<div>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-default btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" data-panel="{{id}}" aria-label="{{labels.panel_opts}}, {{head}}">
|
||||
<i class="fa fa-gear" aria-hidden="true"></i> {{labels.panel_opts}} <span class="fa fa-caret-down" aria-hidden="true"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right panel-opts-{{id}}">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
{{#chart}}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="chart-{{id}}" class="chart-wrap"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{/chart}}
|
||||
{{#table}}
|
||||
<div class="row clearfix table-wrapper {{#autoHideTables}}hidden-xs{{/autoHideTables}}">
|
||||
<div class="col-md-12">
|
||||
<div class="table-responsive">
|
||||
<table data-panel="{{id}}" class="table table-borderless table-hover table-{{id}}">
|
||||
<colgroup>
|
||||
</colgroup>
|
||||
<thead >
|
||||
<tr class="thead-cols"></tr>
|
||||
<tr class="thead-min"></tr>
|
||||
<tr class="thead-max"></tr>
|
||||
<tr class="thead-avg"></tr>
|
||||
</thead>
|
||||
<tbody class="tbody-data">
|
||||
</tbody>
|
||||
<tfoot class="tfoot-totals">
|
||||
<tr class="tfoot-totals">
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<ul class="pagination pagination-sm pull-left">
|
||||
<li class="disabled">
|
||||
<a class="panel-first" href="javascript:void(0);" aria-label="{{labels.first}}" data-panel="{{id}}" title="{{labels.first}}">
|
||||
<i class="fa fa-angle-double-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="disabled">
|
||||
<a class="panel-prev" href="javascript:void(0);" aria-label="{{labels.previous}}" data-panel="{{id}}" title="{{labels.previous}}">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="panel-next" href="javascript:void(0);" aria-label="{{labels.next}}" data-panel="{{id}}" title="{{labels.next}}">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="panel-last" href="javascript:void(0);" aria-label="{{labels.last}}" data-panel="{{id}}" title="{{labels.last}}">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{/table}}
|
||||
</script>
|
||||
|
||||
<!-- TPL Nav Bar wrapper -->
|
||||
<script id="tpl-nav-wrap" type="text/template">
|
||||
<button type="button"class="nav-bars" aria-label="{{ button_menu }}"><i class="fa fa-bars" aria-hidden="true"></i></button>
|
||||
<button type="button"class="nav-gears" aria-label="{{ button_settings }}"><i class="fa fa-cog" aria-hidden="true"></i></button>
|
||||
<i class="nav-ws-status fa fa-stop" aria-live="polite" aria-label="{{ websocket_disconnected }}" title="{{ websocket_disconnected }}"></i>
|
||||
<div class="nav-list"></div>
|
||||
<div class="powered hidden-xs hidden-sm">by <a href="https://goaccess.io/">GoAccess</a> <span>v{{version}}</span> and <a href="https://gwsocket.io/">GWSocket</a></div>
|
||||
</script>
|
||||
|
||||
<!-- TPL Nav Bar items -->
|
||||
<script id="tpl-nav-menu" type="text/template">
|
||||
<h3>{{labels.panels}}</h3>
|
||||
<ul>
|
||||
<!-- Overall stats - not draggable -->
|
||||
<li {{#overall_current}}class="active"{{/overall_current}}>
|
||||
<span class="drag-handle" style="visibility: hidden;">
|
||||
<i class="fa fa-bars"></i>
|
||||
</span>
|
||||
<a href="#"><i class="fa fa-bar-chart"></i> {{labels.thead}}</a>
|
||||
<span title="{{labels.toggle_panel}}" class="toggle-panel {{#overall_hidden}}active{{/overall_hidden}}" data-panel="general"><i class="fa fa-circle-o"></i></span>
|
||||
</li>
|
||||
|
||||
<!-- Panel items - draggable -->
|
||||
{{#nav}}
|
||||
<li {{#current}}class="active"{{/current}}>
|
||||
<span class="drag-handle" title="{{labels.drag_to_reorder}}">
|
||||
<i class="fa fa-bars"></i>
|
||||
</span>
|
||||
<a href="#{{key}}"><i class="fa fa-{{icon}}"></i> {{head}}</a>
|
||||
<span title="{{labels.toggle_panel}}" class="toggle-panel {{#hidden}}active{{/hidden}}" data-panel="{{key}}"><i class="fa fa-circle-o"></i></span>
|
||||
</li>
|
||||
{{/nav}}
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<!-- TPL Nav Bar options -->
|
||||
<script id="tpl-nav-opts" type="text/template">
|
||||
<h3><i class="fa fa-hashtag"></i> {{labels.theme}}</h3>
|
||||
<ul>
|
||||
<li {{#darkGray}}class="active"{{/darkGray}}>
|
||||
<a href="javascript:void(0);" class="theme-dark-gray"><i class="fa fa-circle{{^darkGray}}-o{{/darkGray}}"></i> {{labels.dark_gray}}</a>
|
||||
</li>
|
||||
<li {{#bright}}class="active"{{/bright}}>
|
||||
<a href="javascript:void(0);" class="theme-bright"><i class="fa fa-circle{{^bright}}-o{{/bright}}"></i> {{labels.bright}}</a>
|
||||
</li>
|
||||
<li {{#darkBlue}}class="active"{{/darkBlue}}>
|
||||
<a href="javascript:void(0);" class="theme-dark-blue"><i class="fa fa-circle{{^darkBlue}}-o{{/darkBlue}}"></i> {{labels.dark_blue}}</a>
|
||||
</li>
|
||||
<li {{#darkPurple}}class="active"{{/darkPurple}}>
|
||||
<a href="javascript:void(0);" class="theme-dark-purple"><i class="fa fa-circle{{^darkPurple}}-o{{/darkPurple}}"></i> {{labels.dark_purple}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3><i class="fa fa-list-alt"></i> {{labels.panels}}</h3>
|
||||
<ul class="perpage-wrap">
|
||||
<li class="dropdown-header"><i class="fa fa-list"></i> {{labels.items_per_page}}</li>
|
||||
<li {{#perPage3}}class="active"{{/perPage3}}>
|
||||
<a href="javascript:void(0);" data-perpage="3"><i class="fa fa-circle{{^perPage3}}-o{{/perPage3}}"></i> 3</a>
|
||||
</li>
|
||||
<li {{#perPage5}}class="active"{{/perPage5}}>
|
||||
<a href="javascript:void(0);" data-perpage="5"><i class="fa fa-circle{{^perPage5}}-o{{/perPage5}}"></i> 5</a>
|
||||
</li>
|
||||
<li {{#perPage7}}class="active"{{/perPage7}}>
|
||||
<a href="javascript:void(0);" data-perpage="7"><i class="fa fa-circle{{^perPage7}}-o{{/perPage7}}"></i> 7</a>
|
||||
</li>
|
||||
<li {{#perPage10}}class="active"{{/perPage10}}>
|
||||
<a href="javascript:void(0);" data-perpage="10"><i class="fa fa-circle{{^perPage10}}-o{{/perPage10}}"></i> 10</a>
|
||||
</li>
|
||||
<li {{#perPage15}}class="active"{{/perPage15}}>
|
||||
<a href="javascript:void(0);" data-perpage="15"><i class="fa fa-circle{{^perPage15}}-o{{/perPage15}}"></i> 15</a>
|
||||
</li>
|
||||
<li {{#perPage20}}class="active"{{/perPage20}}>
|
||||
<a href="javascript:void(0);" data-perpage="20"><i class="fa fa-circle{{^perPage20}}-o{{/perPage20}}"></i> 20</a>
|
||||
</li>
|
||||
<li {{#perPage24}}class="active"{{/perPage24}}>
|
||||
<a href="javascript:void(0);" data-perpage="24"><i class="fa fa-circle{{^perPage24}}-o{{/perPage24}}"></i> 24</a>
|
||||
</li>
|
||||
<li class="dropdown-header"><i class="fa fa-table"></i> {{labels.tables}}</li>
|
||||
<li {{#showTables}}class="active"{{/showTables}}>
|
||||
<a href="javascript:void(0);" data-show-tables="1"><i class="fa fa-{{#showTables}}check-{{/showTables}}square-o"></i> {{labels.display_tables}}</a>
|
||||
</li>
|
||||
<li {{#autoHideTables}}class="active"{{/autoHideTables}}>
|
||||
<a href="javascript:void(0);" data-autohide-tables="1" title="{{labels.ah_small_title}}">
|
||||
<i class="fa fa-{{#autoHideTables}}check-{{/autoHideTables}}square-o"></i> {{labels.ah_small}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3><i class="fa fa-th-large"></i> {{labels.layout}}</h3>
|
||||
<ul>
|
||||
<li {{#horizontal}}class="active"{{/horizontal}}>
|
||||
<a href="javascript:void(0);" class="layout-horizontal"><i class="fa fa-circle{{^horizontal}}-o{{/horizontal}}"></i> {{labels.horizontal}}</a>
|
||||
</li>
|
||||
<li {{#wide}}class="active"{{/wide}}>
|
||||
<a href="javascript:void(0);" class="layout-wide"><i class="fa fa-circle{{^wide}}-o{{/wide}}"></i> {{labels.wide}}</a>
|
||||
</li>
|
||||
<li {{#vertical}}class="active"{{/vertical}}>
|
||||
<a href="javascript:void(0);" class="layout-vertical"><i class="fa fa-circle{{^vertical}}-o{{/vertical}}"></i> {{labels.vertical}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h3><i class="fa fa-cog"></i> {{labels.file_opts}}</h3>
|
||||
<ul>
|
||||
<li><a href="javascript:void(0);" class="export-json"><i class="fa fa-code"></i> {{labels.export_json}}</a></li>
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<!-- TPL Chart tooltip -->
|
||||
<script id="tpl-chart-tooltip" type="text/template">
|
||||
<table class="chart-tooltip">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th colspan="2">{{data.0}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="name"><span class="blue"></span>hits</td>
|
||||
<td class="value">{{data.1}}</td>
|
||||
</tr>
|
||||
{{#data.2}}
|
||||
<tr>
|
||||
<td class="name"><span class="red"></span>visitors</td>
|
||||
<td class="value">{{data.2}}</td>
|
||||
</tr>
|
||||
{{/data.2}}
|
||||
</tbody>
|
||||
</table>
|
||||
</script>
|
||||
@@ -0,0 +1,28 @@
|
||||
name: goaccess
|
||||
version: git
|
||||
summary: Real-time web log analyzer and interactive viewer
|
||||
description: |
|
||||
GoAccess is a real-time web log analyzer and interactive viewer that runs
|
||||
in a terminal in *nix systems or through your browser.
|
||||
confinement: classic
|
||||
grade: devel
|
||||
|
||||
apps:
|
||||
goaccess:
|
||||
command: bin/goaccess
|
||||
|
||||
parts:
|
||||
goaccess:
|
||||
plugin: autotools
|
||||
source: .
|
||||
configflags:
|
||||
- --enable-utf8
|
||||
- --enable-geoip=legacy
|
||||
build-packages:
|
||||
- gettext
|
||||
- libncurses-dev
|
||||
- libncursesw5-dev
|
||||
- libgeoip-dev
|
||||
stage-packages:
|
||||
- libgeoip-dev
|
||||
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* base64.c -- A basic base64 encode implementation
|
||||
* _______ _______ __ __
|
||||
* / ____/ | / / ___/____ _____/ /_____ / /_
|
||||
* / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/
|
||||
* / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_
|
||||
* \____/ |__/|__//____/\____/\___/_/|_|\___/\__/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "base64.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
/* Encodes the given data with base64.
|
||||
*
|
||||
* On success, the encoded nul-terminated data, as a string is returned. */
|
||||
char *
|
||||
base64_encode (const void *buf, size_t size) {
|
||||
static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
char *str = (char *) xmalloc ((size + 3) * 4 / 3 + 1);
|
||||
|
||||
char *p = str;
|
||||
const unsigned char *q = (const unsigned char *) buf;
|
||||
size_t i = 0;
|
||||
|
||||
while (i < size) {
|
||||
int c = q[i++];
|
||||
c *= 256;
|
||||
if (i < size)
|
||||
c += q[i];
|
||||
i++;
|
||||
|
||||
c *= 256;
|
||||
if (i < size)
|
||||
c += q[i];
|
||||
i++;
|
||||
|
||||
*p++ = base64[(c & 0x00fc0000) >> 18];
|
||||
*p++ = base64[(c & 0x0003f000) >> 12];
|
||||
|
||||
if (i > size + 1)
|
||||
*p++ = '=';
|
||||
else
|
||||
*p++ = base64[(c & 0x00000fc0) >> 6];
|
||||
|
||||
if (i > size)
|
||||
*p++ = '=';
|
||||
else
|
||||
*p++ = base64[c & 0x0000003f];
|
||||
}
|
||||
|
||||
*p = 0;
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* base64_decode
|
||||
*
|
||||
* Given a Base64 encoded string in 'data', this function decodes it into
|
||||
* a newly allocated binary buffer. The length of the decoded data is stored
|
||||
* in *out_len. The caller is responsible for freeing the returned buffer.
|
||||
*
|
||||
* Returns NULL on error (for example, if the data's length is not a multiple
|
||||
* of 4).
|
||||
*/
|
||||
char *
|
||||
base64_decode (const char *data, size_t *out_len) {
|
||||
size_t decoded_len = 0, i = 0, j = 0, pad = 0, len = 0;
|
||||
char *out = NULL;
|
||||
uint32_t triple = 0;
|
||||
|
||||
/* Create a lookup table for decoding.
|
||||
* For valid Base64 characters 'A'-'Z', 'a'-'z', '0'-'9', '+', '/',
|
||||
* we place their value; for '=' we simply return 0.
|
||||
* All other characters are marked as 0x80 (an invalid marker).
|
||||
*/
|
||||
static const unsigned char dtable[256] = {
|
||||
['A'] = 0,['B'] = 1,['C'] = 2,['D'] = 3,
|
||||
['E'] = 4,['F'] = 5,['G'] = 6,['H'] = 7,
|
||||
['I'] = 8,['J'] = 9,['K'] = 10,['L'] = 11,
|
||||
['M'] = 12,['N'] = 13,['O'] = 14,['P'] = 15,
|
||||
['Q'] = 16,['R'] = 17,['S'] = 18,['T'] = 19,
|
||||
['U'] = 20,['V'] = 21,['W'] = 22,['X'] = 23,
|
||||
['Y'] = 24,['Z'] = 25,
|
||||
['a'] = 26,['b'] = 27,['c'] = 28,['d'] = 29,
|
||||
['e'] = 30,['f'] = 31,['g'] = 32,['h'] = 33,
|
||||
['i'] = 34,['j'] = 35,['k'] = 36,['l'] = 37,
|
||||
['m'] = 38,['n'] = 39,['o'] = 40,['p'] = 41,
|
||||
['q'] = 42,['r'] = 43,['s'] = 44,['t'] = 45,
|
||||
['u'] = 46,['v'] = 47,['w'] = 48,['x'] = 49,
|
||||
['y'] = 50,['z'] = 51,
|
||||
['0'] = 52,['1'] = 53,['2'] = 54,['3'] = 55,
|
||||
['4'] = 56,['5'] = 57,['6'] = 58,['7'] = 59,
|
||||
['8'] = 60,['9'] = 61,
|
||||
['+'] = 62,['/'] = 63,
|
||||
['='] = 0,
|
||||
/* All other values are implicitly 0 (or you can mark them as invalid
|
||||
by setting them to 0x80 if you prefer stricter checking). */
|
||||
};
|
||||
|
||||
len = strlen (data);
|
||||
/* Validate length: Base64 encoded data must be a multiple of 4 */
|
||||
if (len % 4 != 0)
|
||||
return NULL;
|
||||
|
||||
/* Count padding characters at the end */
|
||||
if (len) {
|
||||
if (data[len - 1] == '=')
|
||||
pad++;
|
||||
if (len > 1 && data[len - 2] == '=')
|
||||
pad++;
|
||||
}
|
||||
|
||||
/* Calculate the length of the decoded data */
|
||||
decoded_len = (len / 4) * 3 - pad;
|
||||
out = (char *) xmalloc (decoded_len + 1); /* +1 for a null terminator if needed */
|
||||
|
||||
for (i = 0, j = 0; i < len;) {
|
||||
unsigned int sextet_a = data[i] == '=' ? 0 : dtable[(unsigned char) data[i]];
|
||||
unsigned int sextet_b = data[i + 1] == '=' ? 0 : dtable[(unsigned char) data[i + 1]];
|
||||
unsigned int sextet_c = data[i + 2] == '=' ? 0 : dtable[(unsigned char) data[i + 2]];
|
||||
unsigned int sextet_d = data[i + 3] == '=' ? 0 : dtable[(unsigned char) data[i + 3]];
|
||||
i += 4;
|
||||
|
||||
triple = (sextet_a << 18) | (sextet_b << 12) | (sextet_c << 6) | sextet_d;
|
||||
if (j < decoded_len)
|
||||
out[j++] = (triple >> 16) & 0xFF;
|
||||
if (j < decoded_len)
|
||||
out[j++] = (triple >> 8) & 0xFF;
|
||||
if (j < decoded_len)
|
||||
out[j++] = triple & 0xFF;
|
||||
}
|
||||
|
||||
out[decoded_len] = '\0'; /* Null-terminate the output buffer */
|
||||
if (out_len)
|
||||
*out_len = decoded_len;
|
||||
return out;
|
||||
}
|
||||
|
||||
/*
|
||||
* base64UrlEncode - Converts a standard Base64 encoded string
|
||||
* into Base64Url format.
|
||||
*
|
||||
* This replaces '+' with '-', '/' with '_', and removes '=' padding.
|
||||
*/
|
||||
char *
|
||||
base64UrlEncode (const char *base64) {
|
||||
char *url = NULL;
|
||||
size_t len = 0;
|
||||
if (!base64)
|
||||
return NULL;
|
||||
|
||||
// Duplicate the input string to modify it in-place.
|
||||
url = strdup (base64);
|
||||
if (!url)
|
||||
return NULL;
|
||||
|
||||
// Replace characters: '+' => '-', '/' => '_'
|
||||
for (char *p = url; *p; p++) {
|
||||
if (*p == '+')
|
||||
*p = '-';
|
||||
else if (*p == '/')
|
||||
*p = '_';
|
||||
}
|
||||
|
||||
// Remove trailing '=' characters.
|
||||
len = strlen (url);
|
||||
while (len && url[len - 1] == '=') {
|
||||
url[len - 1] = '\0';
|
||||
len--;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/*
|
||||
* base64UrlDecode - Converts a Base64Url encoded string into standard Base64 format.
|
||||
*
|
||||
* This replaces '-' with '+', '_' with '/', and adds the necessary '=' padding.
|
||||
* Caller is responsible for freeing the returned string.
|
||||
*/
|
||||
char *
|
||||
base64UrlDecode (const char *base64Url) {
|
||||
size_t len = 0, padding = 0, i = 0;
|
||||
char *padded = NULL, *base64 = NULL;
|
||||
|
||||
if (!base64Url)
|
||||
return NULL;
|
||||
|
||||
// Duplicate the input so we can modify it.
|
||||
base64 = strdup (base64Url);
|
||||
if (!base64)
|
||||
return NULL;
|
||||
|
||||
// Replace '-' with '+' and '_' with '/'
|
||||
for (char *p = base64; *p; p++) {
|
||||
if (*p == '-')
|
||||
*p = '+';
|
||||
else if (*p == '_')
|
||||
*p = '/';
|
||||
}
|
||||
|
||||
// Calculate the padding required (base64 length must be a multiple of 4)
|
||||
len = strlen (base64);
|
||||
padding = (4 - (len % 4)) % 4;
|
||||
padded = xmalloc (len + padding + 1);
|
||||
if (!padded) {
|
||||
free (base64);
|
||||
return NULL;
|
||||
}
|
||||
strcpy (padded, base64);
|
||||
for (i = 0; i < padding; i++) {
|
||||
padded[len + i] = '=';
|
||||
}
|
||||
padded[len + padding] = '\0';
|
||||
free (base64);
|
||||
return padded;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* _______ _______ __ __
|
||||
* / ____/ | / / ___/____ _____/ /_____ / /_
|
||||
* / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/
|
||||
* / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_
|
||||
* \____/ |__/|__//____/\____/\___/_/|_|\___/\__/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef BASE64_H_INCLUDED
|
||||
#define BASE64_H_INCLUDED
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
char *base64_encode (const void *buf, size_t size);
|
||||
char *base64_decode (const char *data, size_t *out_len);
|
||||
char *base64UrlDecode (const char *base64Url);
|
||||
char *base64UrlEncode (const char *base64);
|
||||
|
||||
#endif // for #ifndef BASE64_H
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* This is bin2c program, which allows you to convert binary file to
|
||||
* C language array, for use as embedded resource, for instance you can
|
||||
* embed graphics or audio file directly into your program.
|
||||
* This is public domain software, use it on your own risk.
|
||||
* Contact Serge Fukanchik at fuxx@mail.ru if you have any questions.
|
||||
*
|
||||
* Some modifications were made by Gwilym Kuiper (kuiper.gwilym@gmail.com)
|
||||
* I have decided not to change the licence.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef USE_BZ2
|
||||
#include <bzlib.h>
|
||||
#endif
|
||||
|
||||
int
|
||||
main (int argc, char *argv[]) {
|
||||
char *buf;
|
||||
char *ident;
|
||||
int need_comma;
|
||||
long file_size_orig;
|
||||
size_t file_size, i;
|
||||
FILE *f_input, *f_output;
|
||||
|
||||
#ifdef USE_BZ2
|
||||
int status;
|
||||
char *bz2_buf;
|
||||
unsigned int uncompressed_size, bz2_size;
|
||||
#endif
|
||||
|
||||
if (argc < 4) {
|
||||
fprintf (stderr, "Usage: %s binary_file output_file array_name\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
f_input = fopen (argv[1], "rb");
|
||||
if (f_input == NULL) {
|
||||
fprintf (stderr, "%s: can't open %s for reading\n", argv[0], argv[1]);
|
||||
return -1;
|
||||
}
|
||||
// Get the file length
|
||||
fseek (f_input, 0, SEEK_END);
|
||||
file_size_orig = ftell (f_input);
|
||||
if (file_size_orig < 0) {
|
||||
fprintf (stderr, "%s: can't get size of file %s\n", argv[0], argv[1]);
|
||||
fclose (f_input);
|
||||
return -1;
|
||||
}
|
||||
file_size = (size_t) file_size_orig;
|
||||
fseek (f_input, 0, SEEK_SET);
|
||||
|
||||
if ((buf = malloc (file_size)) == NULL) {
|
||||
fprintf (stderr, "Unable to malloc bin2c.c buffer\n");
|
||||
fclose (f_input);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fread (buf, file_size, 1, f_input) != 1) {
|
||||
fprintf (stderr, "%s: can't read from %s\n", argv[0], argv[1]);
|
||||
free (buf);
|
||||
fclose (f_input);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fgetc (f_input) != EOF) {
|
||||
fprintf (stderr, "%s: can't read complete file %s\n", argv[0], argv[1]);
|
||||
free (buf);
|
||||
fclose (f_input);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ferror (f_input)) {
|
||||
fprintf (stderr, "%s: error while reading from %s\n", argv[0], argv[1]);
|
||||
free (buf);
|
||||
fclose (f_input);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose (f_input);
|
||||
|
||||
#ifdef USE_BZ2
|
||||
// allocate for bz2.
|
||||
bz2_size = (file_size + file_size / 100 + 1) + 600; // as per the documentation
|
||||
|
||||
if ((bz2_buf = malloc (bz2_size)) == NULL) {
|
||||
fprintf (stderr, "Unable to malloc bin2c.c buffer\n");
|
||||
free (buf);
|
||||
return -1;
|
||||
}
|
||||
// compress the data
|
||||
status = BZ2_bzBuffToBuffCompress (bz2_buf, &bz2_size, buf, file_size, 9, 1, 0);
|
||||
if (status != BZ_OK) {
|
||||
fprintf (stderr, "Failed to compress data: error %i\n", status);
|
||||
free (buf);
|
||||
free (bz2_buf);
|
||||
return -1;
|
||||
}
|
||||
// and be very lazy
|
||||
free (buf);
|
||||
uncompressed_size = file_size;
|
||||
file_size = bz2_size;
|
||||
buf = bz2_buf;
|
||||
#endif
|
||||
|
||||
f_output = fopen (argv[2], "w");
|
||||
if (f_output == NULL) {
|
||||
fprintf (stderr, "%s: can't open %s for writing\n", argv[0], argv[1]);
|
||||
free (buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ident = argv[3];
|
||||
need_comma = 0;
|
||||
|
||||
fprintf (f_output, "const char %s[%lu] = {", ident, file_size);
|
||||
for (i = 0; i < file_size; ++i) {
|
||||
if (buf[i] == '\0') {
|
||||
fprintf (stderr,
|
||||
"%s: writing a null character terminates the content prematurely\n", argv[0]);
|
||||
fclose (f_output);
|
||||
free (buf);
|
||||
return -1;
|
||||
}
|
||||
if (need_comma)
|
||||
fprintf (f_output, ", ");
|
||||
else
|
||||
need_comma = 1;
|
||||
if ((i % 11) == 0)
|
||||
fprintf (f_output, "\n\t");
|
||||
fprintf (f_output, "0x%.2x", buf[i] & 0xff);
|
||||
}
|
||||
fprintf (f_output, "\n};\n\n");
|
||||
fprintf (f_output, "const int %s_length = %lu;\n", ident, file_size);
|
||||
|
||||
#ifdef USE_BZ2
|
||||
fprintf (f_output, "const int %s_length_uncompressed = %u;\n", ident, uncompressed_size);
|
||||
#endif
|
||||
|
||||
if (ferror (f_output)) {
|
||||
fprintf (stderr, "%s: error while writing to %s\n", argv[0], argv[2]);
|
||||
fclose (f_output);
|
||||
free (buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose (f_output);
|
||||
free (buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* bitmap.c -- A quick bitmap implementation
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
#include "bitmap.h"
|
||||
|
||||
/*
|
||||
* Returns the hamming weight (i.e. the number of bits set) in a word.
|
||||
* NOTE: This routine borrowed from Linux 2.4.9 <linux/bitops.h>.
|
||||
*/
|
||||
static uint32_t
|
||||
hweight (uint32_t w) {
|
||||
uint32_t res;
|
||||
|
||||
res = (w & 0x55555555) + ((w >> 1) & 0x55555555);
|
||||
res = (res & 0x33333333) + ((res >> 2) & 0x33333333);
|
||||
res = (res & 0x0F0F0F0F) + ((res >> 4) & 0x0F0F0F0F);
|
||||
res = (res & 0x00FF00FF) + ((res >> 8) & 0x00FF00FF);
|
||||
res = (res & 0x0000FFFF) + ((res >> 16) & 0x0000FFFF);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/* Returns the number of bytes required for bm->len bits. */
|
||||
inline uint32_t
|
||||
bitmap_sizeof (uint32_t i) {
|
||||
return bitmap_word (i) * sizeof (word_t);
|
||||
}
|
||||
|
||||
void
|
||||
free_bitmap (bitmap *bm) {
|
||||
free ((uint32_t *) bm->bmp);
|
||||
free (bm);
|
||||
}
|
||||
|
||||
int
|
||||
bitmap_set_bit (word_t *words, uint32_t n) {
|
||||
words[WORD_OFFSET (n)] |= ((word_t) 1 << BIT_OFFSET (n));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
bitmap_get_bit (word_t *words, uint32_t n) {
|
||||
word_t bit = words[WORD_OFFSET (n)] & ((word_t) 1 << BIT_OFFSET (n));
|
||||
return bit != 0;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
bitmap_count_set (const bitmap *bm) {
|
||||
uint32_t i, n = 0, len = 0;
|
||||
|
||||
if (!bm)
|
||||
return 0;
|
||||
|
||||
len = bitmap_word (bm->len);
|
||||
for (i = 0; i < len; ++i)
|
||||
n += hweight (bm->bmp[i]);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
bitmap_ffs (bitmap *bm) {
|
||||
uint32_t i, pos = 1, len = 0;
|
||||
uint32_t __bitset;
|
||||
|
||||
if (!bm)
|
||||
return 0;
|
||||
|
||||
len = bitmap_word (bm->len);
|
||||
for (i = 0; i < len; ++i) {
|
||||
__bitset = bm->bmp[i];
|
||||
if ((pos = __builtin_ffs (__bitset)))
|
||||
break;
|
||||
}
|
||||
|
||||
return (BITS_PER_WORD * i) + pos;
|
||||
}
|
||||
|
||||
bitmap *
|
||||
bitmap_create (uint32_t bit) {
|
||||
bitmap *bm = xcalloc (1, sizeof (bitmap));
|
||||
uint32_t bytes = bitmap_sizeof (bit);
|
||||
|
||||
bm->bmp = xmalloc (bytes);
|
||||
memset (bm->bmp, 0, bytes);
|
||||
bm->len = bit;
|
||||
|
||||
return bm;
|
||||
}
|
||||
|
||||
int
|
||||
bitmap_realloc (bitmap *bm, uint32_t bit) {
|
||||
uint32_t *tmp = NULL;
|
||||
uint32_t oldlen = 0, newlen = 0;
|
||||
|
||||
newlen = bitmap_sizeof (bit);
|
||||
oldlen = bitmap_sizeof (bm->len);
|
||||
if (newlen <= oldlen)
|
||||
return 1;
|
||||
|
||||
tmp = realloc (bm->bmp, newlen);
|
||||
if ((tmp == NULL && newlen > 0) || bit < bm->len)
|
||||
FATAL ("Unable to realloc bitmap hash value %u %u", newlen, bm->len);
|
||||
|
||||
LOG_DEBUG (("bit: %d, bm->len: %d, oldlen: %d, newlen: %d\n", bit, bm->len, oldlen, newlen));
|
||||
memset (tmp + bitmap_word (bm->len), 0, (newlen - oldlen));
|
||||
bm->len = bit;
|
||||
bm->bmp = tmp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bitmap *
|
||||
bitmap_copy (const bitmap *bm) {
|
||||
bitmap *ret;
|
||||
|
||||
if (!bm)
|
||||
return NULL;
|
||||
if (!(ret = bitmap_create (bm->len)))
|
||||
return NULL;
|
||||
|
||||
memcpy (ret->bmp, bm->bmp, bitmap_sizeof (bm->len));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
bitmap_key_exists (bitmap *bm, uint32_t bit) {
|
||||
if (bm->len < bit)
|
||||
bitmap_realloc (bm, bit);
|
||||
|
||||
/* if bit set, then it's the same visitor */
|
||||
if (bitmap_get_bit (bm->bmp, bit - 1))
|
||||
return 1;
|
||||
bitmap_set_bit (bm->bmp, bit - 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef BITMAP_H_INCLUDED
|
||||
#define BITMAP_H_INCLUDED
|
||||
|
||||
#include <limits.h> /* for CHAR_BIT */
|
||||
|
||||
typedef uint32_t word_t; // I want to change this, from uint32_t to uint64_t
|
||||
enum { BITS_PER_WORD = sizeof (word_t) * CHAR_BIT };
|
||||
#define WORD_OFFSET(b) ((b) / BITS_PER_WORD)
|
||||
#define BIT_OFFSET(b) ((b) % BITS_PER_WORD)
|
||||
|
||||
typedef struct bitmap_ {
|
||||
uint32_t *bmp;
|
||||
uint32_t len; /** Length of the bitmap, in bits */
|
||||
} bitmap;
|
||||
|
||||
static inline uint32_t
|
||||
bitmap_word (uint32_t i) {
|
||||
return (((i) + (BITS_PER_WORD) - 1) / (BITS_PER_WORD));
|
||||
}
|
||||
|
||||
#define BITMAP_FOREACH(bm, pos, code) { size_t __k; \
|
||||
int __r; uint32_t __bitset, __t; \
|
||||
uint32_t __len = bitmap_word(bm->len); \
|
||||
for (__k = 0; __k < __len; ++__k) { \
|
||||
__bitset = bm->bmp[__k]; \
|
||||
while (__bitset != 0) { \
|
||||
__t = __bitset & -__bitset; \
|
||||
__r = __builtin_ctzl (__bitset); \
|
||||
pos = (__k * 32) + __r + 1; \
|
||||
code; \
|
||||
__bitset ^= __t; \
|
||||
} \
|
||||
} }
|
||||
|
||||
bitmap *bitmap_copy (const bitmap * bm);
|
||||
bitmap *bitmap_create (uint32_t bit);
|
||||
int bitmap_get_bit (word_t * words, uint32_t n);
|
||||
int bitmap_key_exists (bitmap * bm, uint32_t bit);
|
||||
int bitmap_realloc (bitmap * bm, uint32_t bit);
|
||||
int bitmap_set_bit (word_t * words, uint32_t n);
|
||||
uint32_t bitmap_count_set (const bitmap * bm);
|
||||
uint32_t bitmap_ffs (bitmap * bm);
|
||||
uint32_t bitmap_sizeof (uint32_t nbits);
|
||||
void free_bitmap (bitmap * bm);
|
||||
|
||||
#endif // for #ifndef BITMAP_H
|
||||
+658
@@ -0,0 +1,658 @@
|
||||
/**
|
||||
6 browsers.c -- functions for dealing with browsers
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "browsers.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "settings.h"
|
||||
#include "util.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
/* ###NOTE: The size of the list is proportional to the run time,
|
||||
* which makes this pretty slow */
|
||||
|
||||
static char ***browsers_hash = NULL;
|
||||
|
||||
static const char *const browsers[][2] = {
|
||||
/* Game Systems: place game console browsers here */
|
||||
{"Xbox One", "Game Systems"},
|
||||
{"Xbox", "Game Systems"},
|
||||
{"PlayStation", "Game Systems"},
|
||||
{"NintendoBrowser", "Game Systems"},
|
||||
{"Valve Steam", "Game Systems"},
|
||||
{"Origin", "Game Systems"},
|
||||
{"Raptr", "Game Systems"},
|
||||
|
||||
/* Based on Internet Explorer: browsers based on IE engine */
|
||||
{"Avant Browser", "Others"},
|
||||
{"IEMobile", "MSIE"},
|
||||
{"MSIE", "MSIE"},
|
||||
{"Trident/7.0", "MSIE"},
|
||||
{"Edg", "Edge"},
|
||||
{"Edge", "Edge"},
|
||||
|
||||
/* Surf Browser */
|
||||
{"Surf", "Surf"},
|
||||
|
||||
/* Opera: place all opera-related user agents here */
|
||||
{"Opera Mini", "Opera"},
|
||||
{"Opera Mobi", "Opera"},
|
||||
{"Opera", "Opera"},
|
||||
{"OPR", "Opera"},
|
||||
{"OPiOS", "Opera"},
|
||||
{"Coast", "Opera"},
|
||||
|
||||
{"FBAV", "Facebook App"},
|
||||
{"FBSV", "Facebook App"},
|
||||
{"FBAN", "Facebook App"},
|
||||
{"FB_IAB", "Facebook App"},
|
||||
|
||||
/* Others: miscellaneous user agents that don't fit other categories */
|
||||
{"Homebrew", "Others"},
|
||||
{"APT-", "Others"},
|
||||
{"Apt-Cacher", "Others"},
|
||||
{"Aptly", "Others"},
|
||||
{"Chef Client", "Others"},
|
||||
{"Huawei", "Others"},
|
||||
{"HUAWEI", "Others"},
|
||||
{"BlackBerry", "Others"},
|
||||
{"BrowserX", "Others"},
|
||||
{"Dalvik", "Others"},
|
||||
{"Dillo", "Others"},
|
||||
{"ELinks", "Others"},
|
||||
{"Epiphany", "Others"},
|
||||
{"Firebird", "Others"},
|
||||
{"Galeon", "Others"},
|
||||
{"google-cloud-sdk", "Others"},
|
||||
{"IBrowse", "Others"},
|
||||
{"K-Meleon", "Others"},
|
||||
{"Konqueror", "Others"},
|
||||
{"Links", "Others"},
|
||||
{"Lynx", "Others"},
|
||||
{"Midori", "Others"},
|
||||
{"Minefield", "Others"},
|
||||
{"Mosaic", "Others"},
|
||||
{"Netscape", "Others"},
|
||||
{"SeaMonkey", "Others"},
|
||||
{"UCBrowser", "Others"},
|
||||
{"Wget", "Others"},
|
||||
{"libfetch", "Others"},
|
||||
{"check_http", "Others"},
|
||||
{"Go-http-client", "Others"},
|
||||
{"curl", "Others"},
|
||||
{"midori", "Others"},
|
||||
{"w3m", "Others"},
|
||||
{"MicroMessenger", "Others"},
|
||||
{"Apache", "Others"},
|
||||
{"JOSM", "Others"},
|
||||
{"pacman", "Others"},
|
||||
{"Pamac", "Others"},
|
||||
{"libwww-perl", "Others"},
|
||||
{"python-requests", "Others"},
|
||||
{"PackageKit", "Others"},
|
||||
{"F-Droid", "Others"},
|
||||
{"okhttp", "Others"},
|
||||
{"node", "Others"},
|
||||
{"PrivacyBrowser", "Others"},
|
||||
{"Transmission", "Others"},
|
||||
{"libmpv", "Others"},
|
||||
{"aria2", "Others"},
|
||||
{"Electron", "Others"},
|
||||
{"Chromium", "Chrome"},
|
||||
{"QtWebEngine", "Others"},
|
||||
{"SamsungBrowser", "Samsung Internet"},
|
||||
{"Silk/", "Silk"},
|
||||
{"MiuiBrowser", "Others"},
|
||||
{"HuaweiBrowser", "Others"},
|
||||
{"HeyTapBrowser", "Others"},
|
||||
{"VivoBrowser", "Others"},
|
||||
{"QQBrowser", "Others"},
|
||||
{"Instagram", "Others"},
|
||||
{"TikTok", "Others"},
|
||||
{"Snapchat", "Others"},
|
||||
{"Pinterest", "Others"},
|
||||
{"Roku", "Others"},
|
||||
{"Tizen", "Others"},
|
||||
{"Web0S", "Others"},
|
||||
|
||||
/* Feed Readers: place all feed reader user agents here */
|
||||
{"AppleNewsBot", "Feeds"},
|
||||
{"Bloglines", "Feeds"},
|
||||
{"Digg Feed Fetcher", "Feeds"},
|
||||
{"Feedbin", "Feeds"},
|
||||
{"FeedHQ", "Feeds"},
|
||||
{"Feedly", "Feeds"},
|
||||
{"Flipboard", "Feeds"},
|
||||
{"inoreader.com", "Feeds"},
|
||||
{"Netvibes", "Feeds"},
|
||||
{"NewsBlur", "Feeds"},
|
||||
{"PinRSS", "Feeds"},
|
||||
{"theoldreader.com", "Feeds"},
|
||||
{"WordPress.com Reader", "Feeds"},
|
||||
{"YandexBlogs", "Feeds"},
|
||||
{"Brainstorm", "Feeds"},
|
||||
{"Mastodon", "Feeds"},
|
||||
{"Pleroma", "Feeds"},
|
||||
{"Akregator", "Feeds"},
|
||||
{"Apple-PubSub", "Feeds"},
|
||||
{"BTWebClient", "Feeds"},
|
||||
{"com apple.Safari.WebFeedParser", "Feeds"},
|
||||
{"FeedDemon", "Feeds"},
|
||||
{"Feedy", "Feeds"},
|
||||
{"Fever", "Feeds"},
|
||||
{"FreshRSS", "Feeds"},
|
||||
{"Liferea", "Feeds"},
|
||||
{"NetNewsWire", "Feeds"},
|
||||
{"RSSOwl", "Feeds"},
|
||||
{"Thunderbird", "Feeds"},
|
||||
{"Tiny Tiny RSS", "Feeds"},
|
||||
{"Winds", "Feeds"},
|
||||
|
||||
/* Crawlers: all bot and crawler user agents grouped together */
|
||||
{"AdsBot", "Crawlers"},
|
||||
{"AdsBot-Google", "Crawlers"},
|
||||
{"APIs-Google", "Crawlers"},
|
||||
{"AppEngine-Google", "Crawlers"},
|
||||
{"Mediapartners-Google", "Crawlers"},
|
||||
{"Google-InspectionTool", "Crawlers"},
|
||||
{"Google-Extended", "Crawlers"},
|
||||
{"GoogleOther", "Crawlers"},
|
||||
{"Google-Read-Aloud", "Crawlers"},
|
||||
{"Google", "Crawlers"},
|
||||
{"WhatsApp", "Crawlers"},
|
||||
{"AppleBot", "Crawlers"},
|
||||
{"Applebot-Extended", "Crawlers"},
|
||||
{"facebookexternalhit", "Crawlers"},
|
||||
{"Facebot", "Crawlers"},
|
||||
{"Twitter", "Crawlers"},
|
||||
{"Twitterbot", "Crawlers"},
|
||||
{"Slackbot-LinkExpanding", "Crawlers"},
|
||||
{"Slack-ImgProxy", "Crawlers"},
|
||||
{"Slack", "Crawlers"},
|
||||
{"TelegramBot", "Crawlers"},
|
||||
{"Sogou", "Crawlers"},
|
||||
{"Java", "Crawlers"},
|
||||
{"Jakarta Commons-HttpClient", "Crawlers"},
|
||||
{"netEstate", "Crawlers"},
|
||||
{"PiplBot", "Crawlers"},
|
||||
{"Baidu", "Crawlers"},
|
||||
{"Mastodon", "Crawlers"},
|
||||
{"IstellaBot", "Crawlers"},
|
||||
{"heritrix", "Crawlers"},
|
||||
{"PagesInventory", "Crawlers"},
|
||||
{"rogerbot", "Crawlers"},
|
||||
{"fastbot", "Crawlers"},
|
||||
{"yacybot", "Crawlers"},
|
||||
{"PycURL", "Crawlers"},
|
||||
{"PHP", "Crawlers"},
|
||||
{"ClaudeBot", "Crawlers"},
|
||||
{"Claude-User", "Crawlers"},
|
||||
{"AndroidDownloadManager", "Crawlers"},
|
||||
{"Embedly", "Crawlers"},
|
||||
{"ruby", "Crawlers"},
|
||||
{"Ruby", "Crawlers"},
|
||||
{"python", "Crawlers"},
|
||||
{"Python", "Crawlers"},
|
||||
{"LinkedIn", "Crawlers"},
|
||||
{"LinkedInBot", "Crawlers"},
|
||||
{"Microsoft-WebDAV", "Crawlers"},
|
||||
{"DuckDuckBot", "Crawlers"},
|
||||
{"bingbot", "Crawlers"},
|
||||
{"PetalBot", "Crawlers"},
|
||||
{"Discordbot", "Crawlers"},
|
||||
{"ZoominfoBot", "Crawlers"},
|
||||
{"Googlebot", "Crawlers"},
|
||||
{"DotBot", "Crawlers"},
|
||||
{"AhrefsBot", "Crawlers"},
|
||||
{"SemrushBot", "Crawlers"},
|
||||
{"archive.org_bot", "Crawlers"},
|
||||
{"BLEXBot", "Crawlers"},
|
||||
{"NetcraftSurveyAgent", "Crawlers"},
|
||||
{"Netcraft Web Server Survey", "Crawlers"},
|
||||
{"masscan", "Crawlers"},
|
||||
{"MJ12bot", "Crawlers"},
|
||||
{"Pandalytics", "Crawlers"},
|
||||
{"YandexBot", "Crawlers"},
|
||||
{"Nimbostratus-Bot", "Crawlers"},
|
||||
{"HTTP Banner Detection", "Crawlers"},
|
||||
{"Hakai", "Crawlers"},
|
||||
{"WinHttp_WINHTTPREQUEST5", "Crawlers"},
|
||||
{"NetSystemsResearch", "Crawlers"},
|
||||
{"Nextcloud Server Crawler", "Crawlers"},
|
||||
{"CFNetwork", "Crawlers"},
|
||||
{"GoScraper", "Crawlers"},
|
||||
{"Googlebot-Image", "Crawlers"},
|
||||
{"ZmEu", "Crawlers"},
|
||||
{"DowntimeDetector", "Crawlers"},
|
||||
{"MauiBot", "Crawlers"},
|
||||
{"stagefright", "Crawlers"},
|
||||
{"ImagesiftBot", "Crawlers"},
|
||||
{"Bytespider", "Crawlers"},
|
||||
{"WordPress/", "Crawlers"},
|
||||
{"Synapse", "Crawlers"},
|
||||
{"Yahoo! Slurp", "Crawlers"},
|
||||
{"GoogleMobile", "Crawlers"},
|
||||
{"ZoteroTranslationServer", "Crawlers"},
|
||||
{"OAI-SearchBot", "Crawlers"},
|
||||
{"GPTBot", "Crawlers"},
|
||||
{"PerplexityBot", "Crawlers"},
|
||||
{"Amazonbot", "Crawlers"},
|
||||
{"SkypeUriPreview", "Crawlers"},
|
||||
{"OAI-SearchBot", "Crawlers"},
|
||||
{"OAI-SearchBot", "Crawlers"}, /* duplicate-safe if you dedupe elsewhere */
|
||||
|
||||
/* Based on Firefox: place all Firefox-based browsers here */
|
||||
{"Camino", "Others"},
|
||||
{"Iceweasel", "Firefox"},
|
||||
{"Waterfox", "Firefox"},
|
||||
{"PaleMoon", "Firefox"},
|
||||
{"Focus", "Firefox"},
|
||||
{"Klar", "Firefox"},
|
||||
{"Zen/", "Zen"},
|
||||
{"Firefox", "Firefox"},
|
||||
|
||||
/* Based on Chromium: place all Chromium-based browsers here, including BeakerBrowser under Others for consistency */
|
||||
{"Arc/", "Arc"},
|
||||
{"Brave", "Brave"},
|
||||
{"Vivaldi", "Vivaldi"},
|
||||
{"YaBrowser", "Yandex"},
|
||||
{"DuckDuckGo/", "DuckDuckGo Browser"},
|
||||
|
||||
/* Chrome: place all Chrome-related user agents here */
|
||||
{"HeadlessChrome", "Chrome"},
|
||||
{"Chrome", "Chrome"},
|
||||
{"CriOS", "Chrome"},
|
||||
|
||||
/* Safari: place all Safari-related user agents here */
|
||||
{"Safari", "Safari"},
|
||||
|
||||
/* HTTP Library or HTTP Server User Agents: place here */
|
||||
{"axios", "HTTP Library"}, /* NodeJS axios */
|
||||
{"lua-resty-http", "HTTP Library"}, /* Ngx luarestyhttp module */
|
||||
{"Apache-HttpClient", "HTTP Library"},
|
||||
{"GuzzleHttp", "HTTP Library"},
|
||||
{"RestSharp", "HTTP Library"},
|
||||
{"http.rb", "HTTP Library"},
|
||||
{"Java-http-client", "HTTP Library"},
|
||||
{"node-fetch", "HTTP Library"},
|
||||
|
||||
/* Citation Services: place here */
|
||||
{"Citoid", "Citation"}, /* MediaWiki Citoid */
|
||||
{"EasyBib", "Citation"}, /* Easybib */
|
||||
|
||||
/* Podcast Fetchers: place here */
|
||||
{"Downcast", "Podcasts"},
|
||||
{"gPodder", "Podcasts"},
|
||||
{"Instacast", "Podcasts"},
|
||||
{"iTunes", "Podcasts"},
|
||||
{"Miro", "Podcasts"},
|
||||
{"Pocket Casts", "Podcasts"},
|
||||
{"BashPodder", "Podcasts"},
|
||||
|
||||
/* Uptime and Monitoring Clients: place here */
|
||||
{"Pingdom.com", "Uptime"},
|
||||
{"jetmon", "Uptime"},
|
||||
{"NodeUptime", "Uptime"},
|
||||
{"NewRelicPinger", "Uptime"},
|
||||
{"StatusCake", "Uptime"},
|
||||
{"internetVista", "Uptime"},
|
||||
{"Server Density Service Monitoring v2", "Uptime"},
|
||||
{"Better Uptime Bot", "Uptime"},
|
||||
{"Site24x7", "Uptime"},
|
||||
{"Uptime-Kuma", "Uptime"},
|
||||
{"UptimeRobot", "Uptime"},
|
||||
{"NewRelicSynthetics", "Uptime"},
|
||||
{"DatadogSynthetics", "Uptime"},
|
||||
{"ELB-HealthChecker", "Uptime"},
|
||||
{"GoogleHC", "Uptime"},
|
||||
{"kube-probe", "Uptime"},
|
||||
{"Fastly-HealthChecker", "Uptime"},
|
||||
{"Cloudflare-Healthchecks", "Uptime"},
|
||||
{"CloudFlare-AlwaysOnline", "Uptime"},
|
||||
|
||||
/* Performance and Caching: place here */
|
||||
{"ShortPixel", "Performance"}, /* Image Optimization */
|
||||
{"WP Rocket", "Caching"}, /* Preloading Cache for WordPress Plugin */
|
||||
|
||||
/* Security: place here */
|
||||
{"Barracuda Sentinel", "Security"}, /* Barricuda spear fishing service */
|
||||
{"ACI Site Scanner", "Security"}, /* Can't confirm specific vendor */
|
||||
|
||||
/* Mozilla: catch-all for Mozilla-related user agents */
|
||||
{"mozilla", "Others"}
|
||||
};
|
||||
|
||||
/* Free all browser entries from our array of key/value pairs. */
|
||||
void
|
||||
free_browsers_hash (void) {
|
||||
size_t i;
|
||||
int j;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE (browsers); ++i) {
|
||||
free (browsers_hash[i][0]);
|
||||
free (browsers_hash[i][1]);
|
||||
free (browsers_hash[i]);
|
||||
}
|
||||
free (browsers_hash);
|
||||
|
||||
|
||||
for (j = 0; j < conf.browsers_hash_idx; ++j) {
|
||||
free (conf.user_browsers_hash[j][0]);
|
||||
free (conf.user_browsers_hash[j][1]);
|
||||
free (conf.user_browsers_hash[j]);
|
||||
}
|
||||
if (conf.browsers_file) {
|
||||
free (conf.user_browsers_hash);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
is_dup (char ***list, int len, const char *browser) {
|
||||
int i;
|
||||
/* check for dups */
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (strcmp (browser, list[i][0]) == 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set a browser/type pair into our multidimensional array of browsers.
|
||||
*
|
||||
* On duplicate functions returns void.
|
||||
* Otherwise memory is mallo'd for our array entry. */
|
||||
static void
|
||||
set_browser (char ***list, int idx, const char *browser, const char *type) {
|
||||
list[idx] = xcalloc (2, sizeof (char *));
|
||||
list[idx][0] = xstrdup (browser);
|
||||
list[idx][1] = xstrdup (type);
|
||||
}
|
||||
|
||||
/* Parse the key/value pair from the browser list file. */
|
||||
static void
|
||||
parse_browser_token (char ***list, char *line, int n) {
|
||||
char *val;
|
||||
size_t idx = 0;
|
||||
|
||||
/* key */
|
||||
idx = strcspn (line, "\t");
|
||||
if (strlen (line) == idx)
|
||||
FATAL ("Malformed browser name at line: %d", n);
|
||||
|
||||
line[idx] = '\0';
|
||||
|
||||
/* value */
|
||||
val = line + (idx + 1);
|
||||
idx = strspn (val, "\t");
|
||||
if (strlen (val) == idx)
|
||||
FATAL ("Malformed browser category at line: %d", n);
|
||||
val = val + idx;
|
||||
val = trim_str (val);
|
||||
|
||||
if (is_dup (list, conf.browsers_hash_idx, line)) {
|
||||
LOG_INVALID (("Duplicate browser entry: %s", line));
|
||||
return;
|
||||
}
|
||||
|
||||
set_browser (list, conf.browsers_hash_idx, line, val);
|
||||
conf.browsers_hash_idx++;
|
||||
}
|
||||
|
||||
/* Parse our default array of browsers and put them on our hash including those
|
||||
* from the custom parsed browsers file.
|
||||
*
|
||||
* On error functions returns void.
|
||||
* Otherwise browser entries are put into the hash. */
|
||||
void
|
||||
parse_browsers_file (void) {
|
||||
char line[MAX_LINE_BROWSERS + 1];
|
||||
FILE *file;
|
||||
int n = 0;
|
||||
size_t i, len = ARRAY_SIZE (browsers);
|
||||
|
||||
browsers_hash = xmalloc (ARRAY_SIZE (browsers) * sizeof (char **));
|
||||
/* load hash from the browser's array (default) */
|
||||
for (i = 0; i < len; ++i) {
|
||||
set_browser (browsers_hash, i, browsers[i][0], browsers[i][1]);
|
||||
}
|
||||
|
||||
if (!conf.browsers_file)
|
||||
return;
|
||||
|
||||
/* could not open browsers file */
|
||||
if ((file = fopen (conf.browsers_file, "r")) == NULL)
|
||||
FATAL ("Unable to open browser's file: %s", strerror (errno));
|
||||
|
||||
conf.user_browsers_hash = xmalloc (MAX_CUSTOM_BROWSERS * sizeof (char **));
|
||||
/* load hash from the user's given browsers file */
|
||||
while (fgets (line, sizeof line, file) != NULL) {
|
||||
while (line[0] == ' ' || line[0] == '\t')
|
||||
memmove (line, line + 1, strlen (line));
|
||||
n++;
|
||||
|
||||
if (line[0] == '\n' || line[0] == '\r' || line[0] == '#')
|
||||
continue;
|
||||
if (conf.browsers_hash_idx >= MAX_CUSTOM_BROWSERS)
|
||||
FATAL ("Maximum number of custom browsers has been reached");
|
||||
parse_browser_token (conf.user_browsers_hash, line, n);
|
||||
}
|
||||
fclose (file);
|
||||
}
|
||||
|
||||
/* Determine if the user-agent is a crawler.
|
||||
*
|
||||
* On error or is not a crawler, 0 is returned.
|
||||
* If it is a crawler, 1 is returned . */
|
||||
int
|
||||
is_crawler (const char *agent) {
|
||||
char btype[BROWSER_TYPE_LEN];
|
||||
char *browser, *a;
|
||||
|
||||
if (agent == NULL || *agent == '\0')
|
||||
return 0;
|
||||
|
||||
if ((a = xstrdup (agent), browser = verify_browser (a, btype)) != NULL)
|
||||
free (browser);
|
||||
free (a);
|
||||
|
||||
return strcmp (btype, "Crawlers") == 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/* Return the Opera 15 and beyond.
|
||||
*
|
||||
* On success, the opera string and version is returned. */
|
||||
static char *
|
||||
parse_opera (char *token) {
|
||||
char *val = xmalloc (snprintf (NULL, 0, "Opera%s", token) + 1);
|
||||
sprintf (val, "Opera%s", token);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/* Given the original user agent string, and a partial crawler match, iterate
|
||||
* back until the next delimiter is found and return occurrence.
|
||||
*
|
||||
* On error when attempting to extract crawler, NULL is returned.
|
||||
* If a possible crawler string is matched, then possible bot is returned . */
|
||||
static char *
|
||||
parse_crawler (char *str, char *match, char *type) {
|
||||
char *ptr = NULL;
|
||||
int found = 0;
|
||||
|
||||
while (match != str) {
|
||||
match--;
|
||||
if (*match == ' ' || *match == '+' || match == str) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* same addr */
|
||||
if (!found && match == str)
|
||||
return NULL;
|
||||
|
||||
/* account for the previous +|space */
|
||||
if (found && match != str)
|
||||
match++;
|
||||
|
||||
if ((ptr = strpbrk (match, "; ")))
|
||||
*ptr = '\0';
|
||||
/* empty string after parsing it */
|
||||
if (*match == '\0')
|
||||
return NULL;
|
||||
|
||||
xstrncpy (type, "Crawlers", BROWSER_TYPE_LEN);
|
||||
|
||||
return xstrdup (match);
|
||||
}
|
||||
|
||||
/* If the following string matches are found within user agent, then it's
|
||||
* highly likely it's a possible crawler.
|
||||
* Note that this could certainly return false positives.
|
||||
*
|
||||
* If no occurrences are found, NULL is returned.
|
||||
* If an occurrence is found, a pointer to the match is returned . */
|
||||
static char *
|
||||
check_http_crawler (const char *str) {
|
||||
char *match = NULL;
|
||||
|
||||
/* e.g., compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm */
|
||||
if ((match = strstr (str, "; +http")))
|
||||
return match;
|
||||
/* compatible; UptimeRobot/2.0; http://www.uptimerobot.com/ */
|
||||
if ((match = strstr (str, "; http")))
|
||||
return match;
|
||||
/* Slack-ImgProxy (+https://api.slack.com/robots) */
|
||||
if ((match = strstr (str, " (+http")))
|
||||
return match;
|
||||
/* TurnitinBot/3.0 (http://www.turnitin.com/robot/crawlerinfo.html) */
|
||||
if ((match = strstr (str, " (http")))
|
||||
return match;
|
||||
/* w3c e.g., (compatible;+Googlebot/2.1;++http://www.google.com/bot.html) */
|
||||
if ((match = strstr (str, ";++http")))
|
||||
return match;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Parse the given user agent match and extract the browser string.
|
||||
*
|
||||
* If no match, the original match is returned.
|
||||
* Otherwise the parsed browser is returned. */
|
||||
static char *
|
||||
parse_browser (char *match, char *type, int i, char ***hash) {
|
||||
char *b = NULL, *ptr = NULL, *slh = NULL;
|
||||
size_t cnt = 0, space = 0;
|
||||
|
||||
match = char_replace (match, '+', '-');
|
||||
/* Check if there are spaces in the token string, that way strpbrk
|
||||
* does not stop at the first space within the token string */
|
||||
if ((cnt = count_matches (hash[i][0], ' ')) && (b = match)) {
|
||||
while (space++ < cnt && (b = strchr (b, ' ')))
|
||||
b++;
|
||||
} else
|
||||
b = match;
|
||||
|
||||
xstrncpy (type, hash[i][1], BROWSER_TYPE_LEN);
|
||||
/* Internet Explorer 11 */
|
||||
if (strstr (match, "rv:11") && strstr (match, "Trident/7.0")) {
|
||||
return alloc_string ("MSIE/11.0");
|
||||
}
|
||||
/* Opera +15 uses OPR/# */
|
||||
if (strstr (match, "OPR") != NULL && (slh = strrchr (match, '/'))) {
|
||||
return parse_opera (slh);
|
||||
}
|
||||
/* Opera has the version number at the end */
|
||||
if (strstr (match, "Opera") && (slh = strrchr (match, '/')) && match < slh) {
|
||||
memmove (match + 5, slh, strlen (slh) + 1);
|
||||
}
|
||||
/* IE Old */
|
||||
if (strstr (match, "MSIE") != NULL) {
|
||||
if ((ptr = strpbrk (match, ";)-")) != NULL)
|
||||
*ptr = '\0';
|
||||
match = char_replace (match, ' ', '/');
|
||||
}
|
||||
/* all others */
|
||||
else if ((ptr = strpbrk (b ? b : match, ";) \\")) != NULL) {
|
||||
*ptr = '\0';
|
||||
}
|
||||
|
||||
return alloc_string (match);
|
||||
}
|
||||
|
||||
/* Given a user agent, determine the browser used.
|
||||
*
|
||||
* ###NOTE: The size of the list is proportional to the run time,
|
||||
* which makes this pretty slow
|
||||
*
|
||||
* On error, NULL is returned.
|
||||
* On success, a malloc'd string containing the browser is returned. */
|
||||
char *
|
||||
verify_browser (char *str, char *type) {
|
||||
char *match = NULL, *token = NULL;
|
||||
int i = 0;
|
||||
size_t j = 0;
|
||||
|
||||
if (str == NULL || *str == '\0')
|
||||
return NULL;
|
||||
|
||||
/* check user's list */
|
||||
for (i = 0; i < conf.browsers_hash_idx; ++i) {
|
||||
if ((match = strstr (str, conf.user_browsers_hash[i][0])) == NULL)
|
||||
continue;
|
||||
return parse_browser (match, type, i, conf.user_browsers_hash);
|
||||
}
|
||||
|
||||
/* fallback to default browser list */
|
||||
for (j = 0; j < ARRAY_SIZE (browsers); ++j) {
|
||||
if ((match = strstr (str, browsers_hash[j][0])) == NULL)
|
||||
continue;
|
||||
return parse_browser (match, type, j, browsers_hash);
|
||||
}
|
||||
|
||||
/* try heuristics */
|
||||
if ((match = check_http_crawler (str)) && (token = parse_crawler (str, match, type)))
|
||||
return token;
|
||||
|
||||
if (conf.unknowns_log)
|
||||
LOG_UNKNOWNS (("%-7s%s\n", "[BR]", str));
|
||||
|
||||
if (conf.unknowns_as_crawlers && strcmp (type, "Crawlers"))
|
||||
xstrncpy (type, "Crawlers", BROWSER_TYPE_LEN);
|
||||
else
|
||||
xstrncpy (type, "Unknown", BROWSER_TYPE_LEN);
|
||||
|
||||
return alloc_string ("Unknown");
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef BROWSERS_H_INCLUDED
|
||||
#define BROWSERS_H_INCLUDED
|
||||
|
||||
#define BROWSER_TYPE_LEN 24
|
||||
#define MAX_LINE_BROWSERS 128
|
||||
#define MAX_CUSTOM_BROWSERS 256
|
||||
|
||||
/* Each Browser contains the number of hits and the Browser's type */
|
||||
typedef struct GBrowser_ {
|
||||
char browser_type[BROWSER_TYPE_LEN];
|
||||
int hits;
|
||||
} GBrowser;
|
||||
|
||||
char *verify_browser (char *str, char *browser_type);
|
||||
int is_crawler (const char *agent);
|
||||
void free_browsers_hash (void);
|
||||
void parse_browsers_file (void);
|
||||
|
||||
#endif
|
||||
+841
@@ -0,0 +1,841 @@
|
||||
/**
|
||||
* color.c -- functions related to custom color
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "color.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "gslist.h"
|
||||
#include "util.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
static GSLList *color_list = NULL;
|
||||
static GSLList *pair_list = NULL;
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
static const GEnum CSTM_COLORS[] = {
|
||||
{"COLOR_MTRC_HITS" , COLOR_MTRC_HITS},
|
||||
{"COLOR_MTRC_VISITORS" , COLOR_MTRC_VISITORS},
|
||||
{"COLOR_MTRC_HITS_PERC" , COLOR_MTRC_HITS_PERC},
|
||||
{"COLOR_MTRC_VISITORS_PERC" , COLOR_MTRC_VISITORS_PERC},
|
||||
{"COLOR_MTRC_BW_PERC" , COLOR_MTRC_BW_PERC},
|
||||
{"COLOR_MTRC_BW" , COLOR_MTRC_BW},
|
||||
{"COLOR_MTRC_AVGTS" , COLOR_MTRC_AVGTS},
|
||||
{"COLOR_MTRC_CUMTS" , COLOR_MTRC_CUMTS},
|
||||
{"COLOR_MTRC_MAXTS" , COLOR_MTRC_MAXTS},
|
||||
{"COLOR_MTRC_PROT" , COLOR_MTRC_PROT},
|
||||
{"COLOR_MTRC_MTHD" , COLOR_MTRC_MTHD},
|
||||
{"COLOR_MTRC_DATA" , COLOR_MTRC_DATA},
|
||||
{"COLOR_MTRC_HITS_PERC_MAX" , COLOR_MTRC_HITS_PERC_MAX},
|
||||
{"COLOR_MTRC_VISITORS_PERC_MAX" , COLOR_MTRC_VISITORS_PERC_MAX},
|
||||
{"COLOR_MTRC_BW_PERC_MAX" , COLOR_MTRC_BW_PERC_MAX},
|
||||
{"COLOR_PANEL_COLS" , COLOR_PANEL_COLS},
|
||||
{"COLOR_BARS" , COLOR_BARS},
|
||||
{"COLOR_SUBBARS" , COLOR_SUBBARS},
|
||||
{"COLOR_CHART_AXIS" , COLOR_CHART_AXIS},
|
||||
{"COLOR_CHART_VALUES" , COLOR_CHART_VALUES},
|
||||
{"COLOR_ERROR" , COLOR_ERROR},
|
||||
{"COLOR_SELECTED" , COLOR_SELECTED},
|
||||
{"COLOR_PANEL_ACTIVE" , COLOR_PANEL_ACTIVE},
|
||||
{"COLOR_PANEL_HEADER" , COLOR_PANEL_HEADER},
|
||||
{"COLOR_PANEL_DESC" , COLOR_PANEL_DESC},
|
||||
{"COLOR_OVERALL_LBLS" , COLOR_OVERALL_LBLS},
|
||||
{"COLOR_OVERALL_VALS" , COLOR_OVERALL_VALS},
|
||||
{"COLOR_OVERALL_PATH" , COLOR_OVERALL_PATH},
|
||||
{"COLOR_ACTIVE_LABEL" , COLOR_ACTIVE_LABEL},
|
||||
{"COLOR_BG" , COLOR_BG},
|
||||
{"COLOR_DEFAULT" , COLOR_DEFAULT},
|
||||
{"COLOR_PROGRESS" , COLOR_PROGRESS},
|
||||
};
|
||||
|
||||
static const char *const colors256_mono[] = {
|
||||
"COLOR_MTRC_HITS color7:color-1",
|
||||
"COLOR_MTRC_VISITORS color8:color-1",
|
||||
"COLOR_MTRC_DATA color7:color-1",
|
||||
"COLOR_MTRC_BW color8:color-1",
|
||||
"COLOR_MTRC_AVGTS color8:color-1",
|
||||
"COLOR_MTRC_CUMTS color8:color-1",
|
||||
"COLOR_MTRC_MAXTS color8:color-1",
|
||||
"COLOR_MTRC_PROT color8:color-1",
|
||||
"COLOR_MTRC_MTHD color7:color-1",
|
||||
"COLOR_MTRC_HITS_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold VISITORS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold OS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold BROWSERS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold VISIT_TIMES",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold VISITORS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold OS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold BROWSERS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold VISIT_TIMES",
|
||||
"COLOR_MTRC_VISITORS_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_VISITORS_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_MTRC_BW_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_BW_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_PANEL_COLS color7:color-1",
|
||||
"COLOR_BARS color7:color-1",
|
||||
"COLOR_SUBBARS color8:color-1",
|
||||
"COLOR_CHART_AXIS color7:color-1",
|
||||
"COLOR_CHART_VALUES color7:color-1",
|
||||
"COLOR_ERROR color7:color1",
|
||||
"COLOR_SELECTED color7:color8",
|
||||
"COLOR_PANEL_ACTIVE color0:color3",
|
||||
"COLOR_PANEL_HEADER color0:color7",
|
||||
"COLOR_PANEL_DESC color7:color-1",
|
||||
"COLOR_OVERALL_LBLS color7:color-1 bold",
|
||||
"COLOR_OVERALL_VALS color6:color-1 bold",
|
||||
"COLOR_OVERALL_PATH color3:color-1",
|
||||
"COLOR_ACTIVE_LABEL color4:color7",
|
||||
"COLOR_BG color7:color-1",
|
||||
"COLOR_DEFAULT color7:color-1",
|
||||
"COLOR_PROGRESS color0:color6",
|
||||
};
|
||||
|
||||
static const char *const colors256_green[] = {
|
||||
"COLOR_MTRC_HITS color7:color-1",
|
||||
"COLOR_MTRC_VISITORS color8:color-1",
|
||||
"COLOR_MTRC_DATA color7:color-1",
|
||||
"COLOR_MTRC_BW color8:color-1",
|
||||
"COLOR_MTRC_AVGTS color8:color-1",
|
||||
"COLOR_MTRC_CUMTS color8:color-1",
|
||||
"COLOR_MTRC_MAXTS color8:color-1",
|
||||
"COLOR_MTRC_PROT color8:color-1",
|
||||
"COLOR_MTRC_MTHD color7:color-1",
|
||||
"COLOR_MTRC_HITS_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold VISITORS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold OS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold BROWSERS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold VISIT_TIMES",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold VISITORS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold OS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold BROWSERS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold VISIT_TIMES",
|
||||
"COLOR_MTRC_VISITORS_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_VISITORS_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_MTRC_BW_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_BW_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_PANEL_COLS color7:color-1",
|
||||
"COLOR_BARS color7:color-1",
|
||||
"COLOR_SUBBARS color8:color-1",
|
||||
"COLOR_CHART_AXIS color7:color-1",
|
||||
"COLOR_CHART_VALUES color7:color-1",
|
||||
"COLOR_ERROR color7:color1",
|
||||
"COLOR_SELECTED color7:color8",
|
||||
"COLOR_PANEL_ACTIVE color0:color3",
|
||||
"COLOR_PANEL_HEADER color0:color35",
|
||||
"COLOR_PANEL_DESC color7:color-1",
|
||||
"COLOR_OVERALL_LBLS color7:color-1 bold",
|
||||
"COLOR_OVERALL_VALS color6:color-1 bold",
|
||||
"COLOR_OVERALL_PATH color3:color-1",
|
||||
"COLOR_ACTIVE_LABEL color7:color35",
|
||||
"COLOR_BG color7:color-1",
|
||||
"COLOR_DEFAULT color7:color-1",
|
||||
"COLOR_PROGRESS color0:color6",
|
||||
};
|
||||
|
||||
static const char *const colors256_monokai[] = {
|
||||
"COLOR_MTRC_HITS color197:color-1",
|
||||
"COLOR_MTRC_VISITORS color148:color-1",
|
||||
"COLOR_MTRC_DATA color7:color-1",
|
||||
"COLOR_MTRC_BW color81:color-1",
|
||||
"COLOR_MTRC_AVGTS color247:color-1",
|
||||
"COLOR_MTRC_CUMTS color95:color-1",
|
||||
"COLOR_MTRC_MAXTS color186:color-1",
|
||||
"COLOR_MTRC_PROT color141:color-1",
|
||||
"COLOR_MTRC_MTHD color81:color-1",
|
||||
"COLOR_MTRC_HITS_PERC color186:color-1",
|
||||
"COLOR_MTRC_HITS_PERC color186:color-1 VISITORS",
|
||||
"COLOR_MTRC_HITS_PERC color186:color-1 OS",
|
||||
"COLOR_MTRC_HITS_PERC color186:color-1 BROWSERS",
|
||||
"COLOR_MTRC_HITS_PERC color186:color-1 VISIT_TIMES",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color208:color-1",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color208:color-1 VISITORS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color208:color-1 OS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color208:color-1 BROWSERS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color208:color-1 VISIT_TIMES",
|
||||
"COLOR_MTRC_VISITORS_PERC color187:color-1",
|
||||
"COLOR_MTRC_VISITORS_PERC_MAX color208:color-1",
|
||||
"COLOR_MTRC_BW_PERC color187:color-1",
|
||||
"COLOR_MTRC_BW_PERC_MAX color208:color-1",
|
||||
"COLOR_PANEL_COLS color242:color-1",
|
||||
"COLOR_BARS color240:color-1",
|
||||
"COLOR_SUBBARS color250:color-1",
|
||||
"COLOR_CHART_AXIS color241:color-1",
|
||||
"COLOR_CHART_VALUES color248:color-1",
|
||||
"COLOR_ERROR color231:color197",
|
||||
"COLOR_SELECTED color0:color215",
|
||||
"COLOR_PANEL_ACTIVE color7:color240",
|
||||
"COLOR_PANEL_HEADER color7:color237",
|
||||
"COLOR_PANEL_DESC color242:color-1",
|
||||
"COLOR_OVERALL_LBLS color251:color-1",
|
||||
"COLOR_OVERALL_VALS color148:color-1",
|
||||
"COLOR_OVERALL_PATH color186:color-1",
|
||||
"COLOR_ACTIVE_LABEL color7:color237",
|
||||
"COLOR_BG color7:color-1",
|
||||
"COLOR_DEFAULT color7:color-1",
|
||||
"COLOR_PROGRESS color7:color141",
|
||||
};
|
||||
|
||||
static const char *const colors8_mono[] = {
|
||||
"COLOR_MTRC_HITS color7:color-1",
|
||||
"COLOR_MTRC_VISITORS color0:color-1 bold",
|
||||
"COLOR_MTRC_DATA color7:color-1",
|
||||
"COLOR_MTRC_BW color0:color-1 bold",
|
||||
"COLOR_MTRC_AVGTS color0:color-1 bold",
|
||||
"COLOR_MTRC_CUMTS color0:color-1 bold",
|
||||
"COLOR_MTRC_MAXTS color0:color-1 bold",
|
||||
"COLOR_MTRC_PROT color0:color-1 bold",
|
||||
"COLOR_MTRC_MTHD color7:color-1 ",
|
||||
"COLOR_MTRC_HITS_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold VISITORS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold OS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold BROWSERS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold VISIT_TIMES",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold VISITORS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold OS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold BROWSERS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold VISIT_TIMES",
|
||||
"COLOR_MTRC_VISITORS_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_VISITORS_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_MTRC_BW_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_BW_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_PANEL_COLS color7:color-1",
|
||||
"COLOR_BARS color7:color-1",
|
||||
"COLOR_SUBBARS color8:color-1",
|
||||
"COLOR_CHART_AXIS color7:color-1",
|
||||
"COLOR_CHART_VALUES color7:color-1",
|
||||
"COLOR_ERROR color7:color1",
|
||||
"COLOR_SELECTED color0:color7",
|
||||
"COLOR_PANEL_ACTIVE color0:color3",
|
||||
"COLOR_PANEL_HEADER color0:color7",
|
||||
"COLOR_PANEL_DESC color7:color-1",
|
||||
"COLOR_OVERALL_LBLS color7:color-1 bold",
|
||||
"COLOR_OVERALL_VALS color6:color-1",
|
||||
"COLOR_OVERALL_PATH color3:color-1",
|
||||
"COLOR_ACTIVE_LABEL color4:color7",
|
||||
"COLOR_BG color7:color-1",
|
||||
"COLOR_DEFAULT color7:color-1",
|
||||
"COLOR_PROGRESS color0:color6",
|
||||
};
|
||||
|
||||
static const char *const colors8_green[] = {
|
||||
"COLOR_MTRC_HITS color7:color-1",
|
||||
"COLOR_MTRC_VISITORS color0:color-1 bold",
|
||||
"COLOR_MTRC_DATA color7:color-1",
|
||||
"COLOR_MTRC_BW color0:color-1 bold",
|
||||
"COLOR_MTRC_AVGTS color0:color-1 bold",
|
||||
"COLOR_MTRC_CUMTS color0:color-1 bold",
|
||||
"COLOR_MTRC_MAXTS color0:color-1 bold",
|
||||
"COLOR_MTRC_PROT color0:color-1 bold",
|
||||
"COLOR_MTRC_MTHD color7:color-1 ",
|
||||
"COLOR_MTRC_HITS_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold VISITORS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold OS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold BROWSERS",
|
||||
"COLOR_MTRC_HITS_PERC color1:color-1 bold VISIT_TIMES",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold VISITORS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold OS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold BROWSERS",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color3:color-1 bold VISIT_TIMES",
|
||||
"COLOR_MTRC_VISITORS_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_VISITORS_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_MTRC_BW_PERC color0:color-1 bold",
|
||||
"COLOR_MTRC_BW_PERC_MAX color0:color-1 bold",
|
||||
"COLOR_PANEL_COLS color7:color-1",
|
||||
"COLOR_BARS color2:color-1",
|
||||
"COLOR_SUBBARS color8:color-1",
|
||||
"COLOR_CHART_AXIS color2:color-1",
|
||||
"COLOR_CHART_VALUES color2:color-1",
|
||||
"COLOR_ERROR color7:color1",
|
||||
"COLOR_SELECTED color0:color7",
|
||||
"COLOR_PANEL_ACTIVE color0:color3",
|
||||
"COLOR_PANEL_HEADER color0:color2",
|
||||
"COLOR_PANEL_DESC color7:color-1",
|
||||
"COLOR_OVERALL_LBLS color7:color-1 bold",
|
||||
"COLOR_OVERALL_VALS color6:color-1",
|
||||
"COLOR_OVERALL_PATH color3:color-1",
|
||||
"COLOR_ACTIVE_LABEL color0:color2",
|
||||
"COLOR_BG color7:color-1",
|
||||
"COLOR_DEFAULT color7:color-1",
|
||||
"COLOR_PROGRESS color0:color6",
|
||||
};
|
||||
|
||||
static const char *const nocolors[] = {
|
||||
"COLOR_MTRC_HITS color0:color-1",
|
||||
"COLOR_MTRC_VISITORS color0:color-1",
|
||||
"COLOR_MTRC_DATA color0:color-1",
|
||||
"COLOR_MTRC_BW color0:color-1",
|
||||
"COLOR_MTRC_AVGTS color0:color-1",
|
||||
"COLOR_MTRC_CUMTS color0:color-1",
|
||||
"COLOR_MTRC_MAXTS color0:color-1",
|
||||
"COLOR_MTRC_PROT color0:color-1",
|
||||
"COLOR_MTRC_MTHD color0:color-1",
|
||||
"COLOR_MTRC_HITS_PERC color0:color-1",
|
||||
"COLOR_MTRC_HITS_PERC_MAX color0:color-1",
|
||||
"COLOR_MTRC_VISITORS_PERC color0:color-1",
|
||||
"COLOR_MTRC_VISITORS_PERC_MAX color0:color-1",
|
||||
"COLOR_MTRC_BW_PERC color0:color-1",
|
||||
"COLOR_MTRC_BW_PERC_MAX color0:color-1",
|
||||
"COLOR_PANEL_COLS color0:color-1",
|
||||
"COLOR_BARS color0:color-1",
|
||||
"COLOR_SUBBARS color0:color-1",
|
||||
"COLOR_CHART_AXIS color0:color-1",
|
||||
"COLOR_CHART_VALUES color0:color-1",
|
||||
"COLOR_ERROR color0:color-1",
|
||||
"COLOR_SELECTED color0:color-1 reverse",
|
||||
"COLOR_PANEL_ACTIVE color0:color-1 reverse",
|
||||
"COLOR_PANEL_HEADER color0:color-1 reverse",
|
||||
"COLOR_PANEL_DESC color0:color-1",
|
||||
"COLOR_OVERALL_LBLS color0:color-1",
|
||||
"COLOR_OVERALL_VALS color0:color-1",
|
||||
"COLOR_OVERALL_PATH color0:color-1",
|
||||
"COLOR_ACTIVE_LABEL color0:color-1 reverse",
|
||||
"COLOR_BG color0:color-1",
|
||||
"COLOR_DEFAULT color0:color-1",
|
||||
"COLOR_PROGRESS color0:color-1 reverse",
|
||||
};
|
||||
|
||||
/* *INDENT-ON* */
|
||||
|
||||
/* Allocate memory for color elements */
|
||||
static GColors *
|
||||
new_gcolors (void) {
|
||||
GColors *color = xcalloc (1, sizeof (GColors));
|
||||
color->module = -1;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
/* Allocate memory for a color element properties */
|
||||
static GColorPair *
|
||||
new_gcolorpair (void) {
|
||||
GColorPair *pair = xcalloc (1, sizeof (GColorPair));
|
||||
/* Must be between 2 and COLOR_PAIRS-1.
|
||||
* Starts at 2 since COLOR_NORMAL has already been set */
|
||||
pair->idx = 2;
|
||||
|
||||
return pair;
|
||||
}
|
||||
|
||||
/* Free malloc'd memory for color elements */
|
||||
void
|
||||
free_color_lists (void) {
|
||||
if (pair_list)
|
||||
list_remove_nodes (pair_list);
|
||||
if (color_list)
|
||||
list_remove_nodes (color_list);
|
||||
color_list = NULL;
|
||||
pair_list = NULL;
|
||||
}
|
||||
|
||||
/* Set a default color - COLOR_NORMAL, this will be used if
|
||||
* no colors are supported by the terminal */
|
||||
void
|
||||
set_normal_color (void) {
|
||||
GColorPair *pair = new_gcolorpair ();
|
||||
GColors *color = new_gcolors ();
|
||||
|
||||
pair->idx = 1;
|
||||
pair->fg = COLOR_WHITE;
|
||||
pair->bg = -1;
|
||||
|
||||
color->pair = pair;
|
||||
color->item = COLOR_NORMAL;
|
||||
|
||||
pair_list = list_create (pair);
|
||||
color_list = list_create (color);
|
||||
|
||||
init_pair (pair->idx, pair->fg, pair->bg);
|
||||
}
|
||||
|
||||
/* Get color properties for COLOR_OVERALL_LBLS */
|
||||
GColors *
|
||||
color_overall_lbls (void) {
|
||||
return get_color (COLOR_OVERALL_LBLS);
|
||||
}
|
||||
|
||||
/* Get color properties for COLOR_OVERALL_VALS */
|
||||
GColors *
|
||||
color_overall_vals (void) {
|
||||
return get_color (COLOR_OVERALL_VALS);
|
||||
}
|
||||
|
||||
/* Get color properties for COLOR_OVERALL_PATH */
|
||||
GColors *
|
||||
color_overall_path (void) {
|
||||
return get_color (COLOR_OVERALL_PATH);
|
||||
}
|
||||
|
||||
/* Get color properties for COLOR_PANEL_HEADER */
|
||||
GColors *
|
||||
color_panel_header (void) {
|
||||
return get_color (COLOR_PANEL_HEADER);
|
||||
}
|
||||
|
||||
/* Get color properties for COLOR_PANEL_DESC */
|
||||
GColors *
|
||||
color_panel_desc (void) {
|
||||
return get_color (COLOR_PANEL_DESC);
|
||||
}
|
||||
|
||||
/* Get color properties for COLOR_PANEL_ACTIVE*/
|
||||
GColors *
|
||||
color_panel_active (void) {
|
||||
return get_color (COLOR_PANEL_ACTIVE);
|
||||
}
|
||||
|
||||
/* Get color properties for COLOR_SELECTED */
|
||||
GColors *
|
||||
color_selected (void) {
|
||||
return get_color (COLOR_SELECTED);
|
||||
}
|
||||
|
||||
/* Get color properties for COLOR_PROGRESS */
|
||||
GColors *
|
||||
color_progress (void) {
|
||||
return get_color (COLOR_PROGRESS);
|
||||
}
|
||||
|
||||
/* Get color properties for COLOR_DEFAULT */
|
||||
GColors *
|
||||
color_default (void) {
|
||||
return get_color (COLOR_DEFAULT);
|
||||
}
|
||||
|
||||
/* Get color properties for COLOR_ERROR */
|
||||
GColors *
|
||||
color_error (void) {
|
||||
return get_color (COLOR_ERROR);
|
||||
}
|
||||
|
||||
/* Get the enumerated color given its equivalent color string.
|
||||
*
|
||||
* On error, -1 is returned.
|
||||
* On success, the enumerated color is returned. */
|
||||
static int
|
||||
get_color_item_enum (const char *str) {
|
||||
return str2enum (CSTM_COLORS, ARRAY_SIZE (CSTM_COLORS), str);
|
||||
}
|
||||
|
||||
/* Extract color number from the given config string.
|
||||
*
|
||||
* On error, -2 is returned. If color is greater than max colors, it aborts.
|
||||
* On success, the color number is returned. */
|
||||
static int
|
||||
extract_color (char *color) {
|
||||
char *sEnd;
|
||||
int col = 0;
|
||||
|
||||
if (strncasecmp (color, "color", 5) != 0)
|
||||
return -2;
|
||||
|
||||
color += 5;
|
||||
col = strtol (color, &sEnd, 10);
|
||||
if (color == sEnd || *sEnd != '\0' || errno == ERANGE)
|
||||
return -2;
|
||||
/* ensure used color is supported by the terminal */
|
||||
if (col > COLORS)
|
||||
FATAL ("Terminal doesn't support color: %d - max colors: %d", col, COLORS);
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
/* Assign the background and foreground color number from the given
|
||||
* config string to GColorPair.
|
||||
*
|
||||
* On error, 1 is returned.
|
||||
* On success, 0 is returned. */
|
||||
static int
|
||||
parse_bg_fg_color (GColorPair *pair, const char *value) {
|
||||
char bgcolor[COLOR_STR_LEN] = "", fgcolor[COLOR_STR_LEN] = "";
|
||||
int ret = 0;
|
||||
|
||||
if (sscanf (value, "%8[^:]:%8[^ ]", fgcolor, bgcolor) != 2)
|
||||
return 1;
|
||||
|
||||
if ((pair->bg = extract_color (bgcolor)) == -2)
|
||||
ret = 1;
|
||||
|
||||
if ((pair->fg = extract_color (fgcolor)) == -2)
|
||||
ret = 1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Assign color attributes from the given config string to GColors. */
|
||||
static void
|
||||
locate_attr_color (GColors *color, const char *attr) {
|
||||
if (strstr (attr, "bold"))
|
||||
color->attr |= A_BOLD;
|
||||
if (strstr (attr, "underline"))
|
||||
color->attr |= A_UNDERLINE;
|
||||
if (strstr (attr, "normal"))
|
||||
color->attr |= A_NORMAL;
|
||||
if (strstr (attr, "reverse"))
|
||||
color->attr |= A_REVERSE;
|
||||
if (strstr (attr, "standout"))
|
||||
color->attr |= A_REVERSE;
|
||||
if (strstr (attr, "blink"))
|
||||
color->attr |= A_BLINK;
|
||||
}
|
||||
|
||||
/* Parse color attributes from the given config string.
|
||||
*
|
||||
* On error, 1 is returned.
|
||||
* On success, 0 is returned. */
|
||||
static int
|
||||
parse_attr_color (GColors *color, const char *value) {
|
||||
char *line, *ptr, *start;
|
||||
int ret = 0;
|
||||
|
||||
line = xstrdup (value);
|
||||
|
||||
start = strchr (line, ' ');
|
||||
if ((!start) || (!*(start + 1))) {
|
||||
LOG_DEBUG (("attempted to parse color attr: %s\n", value));
|
||||
goto clean;
|
||||
}
|
||||
|
||||
start++;
|
||||
while (1) {
|
||||
if ((ptr = strpbrk (start, ", ")) != NULL)
|
||||
*ptr = 0;
|
||||
locate_attr_color (color, start);
|
||||
if (ptr == NULL)
|
||||
break;
|
||||
start = ptr + 1;
|
||||
}
|
||||
|
||||
clean:
|
||||
free (line);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Parse color module from the given config string.
|
||||
*
|
||||
* On error, 1 is returned.
|
||||
* On success, 0 is returned. */
|
||||
static int
|
||||
parse_module_color (GColors *color, const char *value) {
|
||||
char *line = xstrdup (value), *p;
|
||||
|
||||
p = strrchr (line, ' ');
|
||||
if (!p || !*(p + 1)) {
|
||||
LOG_DEBUG (("attempted to parse color module: %s\n", value));
|
||||
goto clean;
|
||||
}
|
||||
|
||||
if ((color->module = get_module_enum (p + 1)) == -1)
|
||||
LOG_DEBUG (("attempted to parse color module: %s\n", value));
|
||||
|
||||
clean:
|
||||
free (line);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find a color by item and module attributes on the list of already
|
||||
* parsed colors.
|
||||
*
|
||||
* If color exists, 1 is returned.
|
||||
* If color does not exist, 1 is returned. */
|
||||
static int
|
||||
find_color_in_list (void *data, void *color) {
|
||||
GColors *new_color = color;
|
||||
GColors *old_color = data;
|
||||
|
||||
if (old_color->item != new_color->item)
|
||||
return 0;
|
||||
if (old_color->module != new_color->module)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Find a color by foreground and background attributes on the list of
|
||||
* already parsed colors.
|
||||
*
|
||||
* If color exists, 1 is returned.
|
||||
* If color does not exist, 1 is returned. */
|
||||
static int
|
||||
find_pair_in_list (void *data, void *color) {
|
||||
GColorPair *new_color = color;
|
||||
GColorPair *old_color = data;
|
||||
|
||||
if (old_color->fg != new_color->fg)
|
||||
return 0;
|
||||
if (old_color->bg != new_color->bg)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Compare a color item (GColorItem) that has no module with the given needle
|
||||
* item.
|
||||
*
|
||||
* If the items match and with no module, 1 is returned.
|
||||
* If condition is not satisfied, 0 is returned. */
|
||||
static int
|
||||
find_color_item_in_list (void *data, void *needle) {
|
||||
GColors *color = data;
|
||||
GColorItem *item = needle;
|
||||
|
||||
return color->item == (GColorItem) (*(int *) item) && color->module == -1;
|
||||
}
|
||||
|
||||
/* Compare a color item (GColorItem) and module with the given needle item.
|
||||
*
|
||||
* If the items match and with no module, 1 is returned.
|
||||
* If condition is not satisfied, 0 is returned. */
|
||||
static int
|
||||
find_color_item_module_in_list (void *data, void *needle) {
|
||||
GColors *color = data;
|
||||
GColors *item = needle;
|
||||
|
||||
return color->item == item->item && color->module == item->module;
|
||||
}
|
||||
|
||||
/* Get color item properties given an item (enumerated).
|
||||
*
|
||||
* On error, it aborts.
|
||||
* On success, the color item properties are returned, or NULL if no match
|
||||
* found. */
|
||||
GColors *
|
||||
get_color (GColorItem item) {
|
||||
GColorItem normal = COLOR_NORMAL;
|
||||
GSLList *match = NULL;
|
||||
|
||||
if ((match = list_find (color_list, find_color_item_in_list, &item)))
|
||||
return (GColors *) match->data;
|
||||
|
||||
if ((match = list_find (color_list, find_color_item_in_list, &normal)))
|
||||
return (GColors *) match->data;
|
||||
|
||||
/* should not get here */
|
||||
FATAL ("Unable to find color item %d", item);
|
||||
}
|
||||
|
||||
/* Get color item properties given an item (enumerated) and its module.
|
||||
*
|
||||
* On error, it aborts.
|
||||
* On success, the color item properties are returned, or NULL if no match
|
||||
* found. */
|
||||
GColors *
|
||||
get_color_by_item_module (GColorItem item, GModule module) {
|
||||
GColors *needle = new_gcolors (), *color = NULL;
|
||||
GSLList *match = NULL;
|
||||
|
||||
needle->module = module;
|
||||
needle->item = item;
|
||||
|
||||
/* find color for specific item/module pair */
|
||||
if ((match = list_find (color_list, find_color_item_module_in_list, needle)))
|
||||
color = match->data;
|
||||
|
||||
/* attempt to find color by item (fallback) */
|
||||
if (!color)
|
||||
color = get_color (item);
|
||||
free (needle);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
/* Parse a color definition line from the config file.
|
||||
*
|
||||
* On error, it aborts.
|
||||
* On success, the color properties are assigned */
|
||||
static void
|
||||
parse_color_line (GColorPair *pair, GColors *color, char *line) {
|
||||
char *val;
|
||||
int item = 0;
|
||||
size_t idx;
|
||||
|
||||
/* key */
|
||||
idx = strcspn (line, " \t");
|
||||
if (strlen (line) == idx)
|
||||
FATAL ("Malformed color key at line: %s", line);
|
||||
|
||||
line[idx] = '\0';
|
||||
if ((item = get_color_item_enum (line)) == -1)
|
||||
FATAL ("Unable to find color key: %s", line);
|
||||
|
||||
/* value */
|
||||
val = line + (idx + 1);
|
||||
idx = strspn (val, " \t");
|
||||
if (strlen (val) == idx)
|
||||
FATAL ("Malformed color value at line: %s", line);
|
||||
val = val + idx;
|
||||
|
||||
/* get background/foreground color */
|
||||
if (parse_bg_fg_color (pair, val) == 1)
|
||||
FATAL ("Invalid bg/fg color pairs at: %s %s", line, val);
|
||||
|
||||
if (parse_attr_color (color, val) == 1)
|
||||
FATAL ("Invalid color attrs at: %s %s", line, val);
|
||||
|
||||
if (parse_module_color (color, val) == 1)
|
||||
FATAL ("Invalid color module at: %s %s", line, val);
|
||||
|
||||
color->item = item;
|
||||
}
|
||||
|
||||
/* Attempt to prepend the given color on our color linked list.
|
||||
*
|
||||
* On error, or if color already exists, the given color is freed.
|
||||
* On success, or if not color found, store color properties */
|
||||
static void
|
||||
prepend_color (GColors **color) {
|
||||
/* create a list of colors if one does not exist */
|
||||
if (color_list == NULL) {
|
||||
color_list = list_create (*color);
|
||||
}
|
||||
/* attempt to find the given color data type (by item and attributes) in
|
||||
* our color list */
|
||||
else if (list_find (color_list, find_color_in_list, *color)) {
|
||||
/* if found, free the recently malloc'd color data type and use
|
||||
* existing color */
|
||||
free (*color);
|
||||
*color = NULL;
|
||||
} else {
|
||||
/* not a dup, so insert the new color in our color list */
|
||||
color_list = list_insert_prepend (color_list, *color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse a color definition line from the config file and store it on a single
|
||||
* linked-list.
|
||||
*
|
||||
* On error, it aborts.
|
||||
* On success, the color properties are stored */
|
||||
static void
|
||||
parse_color (char *line) {
|
||||
GSLList *match = NULL;
|
||||
GColors *color = NULL;
|
||||
GColorPair *pair = NULL;
|
||||
|
||||
color = new_gcolors ();
|
||||
pair = new_gcolorpair ();
|
||||
|
||||
/* extract a color pair and color attributes from the given config line */
|
||||
parse_color_line (pair, color, line);
|
||||
|
||||
/* create a pair color list if one doesn't exist */
|
||||
if (pair_list == NULL) {
|
||||
pair_list = list_create (pair);
|
||||
}
|
||||
/* attempt to find the given color pair in our pair list */
|
||||
else if ((match = list_find (pair_list, find_pair_in_list, pair))) {
|
||||
/* pair found, use new pair and free existing one */
|
||||
free (pair);
|
||||
pair = (GColorPair *) match->data;
|
||||
}
|
||||
/* pair not found, use it then */
|
||||
else {
|
||||
pair->idx += list_count (pair_list);
|
||||
pair_list = list_insert_prepend (pair_list, pair);
|
||||
}
|
||||
/* set color pair */
|
||||
color->pair = pair;
|
||||
prepend_color (&color);
|
||||
|
||||
/* if no color pair was found, then we init the color pair */
|
||||
if (!match && color)
|
||||
init_pair (color->pair->idx, color->pair->fg, color->pair->bg);
|
||||
|
||||
free (line);
|
||||
}
|
||||
|
||||
/* Iterate over all color definitions in the config file.
|
||||
*
|
||||
* On error, it aborts.
|
||||
* On success, the color properties are parsed and stored */
|
||||
static void
|
||||
parse_colors (const char *const colors[], size_t n) {
|
||||
char *line;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < n; ++i) {
|
||||
line = strdup (colors[i]);
|
||||
/* did not find a valid format */
|
||||
if (strchr (line, ':') == NULL) {
|
||||
free (line);
|
||||
continue;
|
||||
}
|
||||
parse_color (line);
|
||||
}
|
||||
}
|
||||
|
||||
/* Use default color definitions if necessary. */
|
||||
static void
|
||||
add_default_colors (void) {
|
||||
/* no colors */
|
||||
if (COLORS < 8)
|
||||
parse_colors (nocolors, ARRAY_SIZE (nocolors));
|
||||
|
||||
/* 256 colors, and no color scheme set or set to monokai */
|
||||
if (COLORS == 256 && (!conf.color_scheme || conf.color_scheme == MONOKAI))
|
||||
parse_colors (colors256_monokai, ARRAY_SIZE (colors256_monokai));
|
||||
/* otherwise use 16 colors scheme */
|
||||
else if (COLORS > 16) {
|
||||
if (conf.color_scheme == STD_GREEN)
|
||||
parse_colors (colors256_green, ARRAY_SIZE (colors256_green));
|
||||
else
|
||||
parse_colors (colors256_mono, ARRAY_SIZE (colors256_mono));
|
||||
}
|
||||
|
||||
/* 8 colors */
|
||||
if (COLORS >= 8 && COLORS <= 16) {
|
||||
if (conf.color_scheme == STD_GREEN)
|
||||
parse_colors (colors8_green, ARRAY_SIZE (colors8_green));
|
||||
else
|
||||
parse_colors (colors8_mono, ARRAY_SIZE (colors8_mono));
|
||||
}
|
||||
}
|
||||
|
||||
/* Entry point to parse color definitions or use default colors */
|
||||
void
|
||||
set_colors (int force) {
|
||||
errno = 0;
|
||||
if (conf.color_idx > 0 && !force)
|
||||
parse_colors (conf.colors, conf.color_idx);
|
||||
else
|
||||
add_default_colors ();
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef COLOR_H_INCLUDED
|
||||
#define COLOR_H_INCLUDED
|
||||
|
||||
#define COLOR_STR_LEN 9
|
||||
|
||||
/* Color Items/Fields */
|
||||
typedef enum CSTM_COLORS {
|
||||
COLOR_NORMAL,
|
||||
COLOR_MTRC_HITS,
|
||||
COLOR_MTRC_VISITORS,
|
||||
COLOR_MTRC_DATA,
|
||||
COLOR_MTRC_BW,
|
||||
COLOR_MTRC_AVGTS,
|
||||
COLOR_MTRC_CUMTS,
|
||||
COLOR_MTRC_MAXTS,
|
||||
COLOR_MTRC_PROT,
|
||||
COLOR_MTRC_MTHD,
|
||||
COLOR_MTRC_HITS_PERC,
|
||||
COLOR_MTRC_HITS_PERC_MAX,
|
||||
COLOR_MTRC_VISITORS_PERC,
|
||||
COLOR_MTRC_VISITORS_PERC_MAX,
|
||||
COLOR_MTRC_BW_PERC,
|
||||
COLOR_MTRC_BW_PERC_MAX,
|
||||
COLOR_PANEL_COLS,
|
||||
COLOR_BARS,
|
||||
COLOR_SUBBARS,
|
||||
COLOR_CHART_AXIS,
|
||||
COLOR_CHART_VALUES,
|
||||
COLOR_ERROR,
|
||||
COLOR_SELECTED,
|
||||
COLOR_PANEL_ACTIVE,
|
||||
COLOR_PANEL_HEADER,
|
||||
COLOR_PANEL_DESC,
|
||||
COLOR_OVERALL_LBLS,
|
||||
COLOR_OVERALL_VALS,
|
||||
COLOR_OVERALL_PATH,
|
||||
COLOR_ACTIVE_LABEL,
|
||||
COLOR_BG,
|
||||
COLOR_DEFAULT,
|
||||
COLOR_PROGRESS,
|
||||
} GColorItem;
|
||||
|
||||
/* Default Color Schemes */
|
||||
typedef enum SCHEMES {
|
||||
NO_COLOR,
|
||||
MONOCHROME,
|
||||
STD_GREEN,
|
||||
MONOKAI,
|
||||
} GSchemes;
|
||||
|
||||
#include "commons.h"
|
||||
|
||||
/* Each color properties */
|
||||
typedef struct GColorPair_ {
|
||||
short idx; /* color pair index identifier */
|
||||
short fg; /* foreground color */
|
||||
short bg; /* background color */
|
||||
} GColorPair;
|
||||
|
||||
/* Color */
|
||||
typedef struct GColors_ {
|
||||
GColorItem item; /* screen item */
|
||||
GColorPair *pair; /* color pair */
|
||||
int attr; /* color attributes, e.g., bold */
|
||||
short module; /* panel */
|
||||
int pair_idx;
|
||||
} GColors;
|
||||
|
||||
GColors *color_default (void);
|
||||
GColors *color_error (void);
|
||||
GColors *color_overall_lbls (void);
|
||||
GColors *color_overall_path (void);
|
||||
GColors *color_overall_vals (void);
|
||||
GColors *color_panel_active (void);
|
||||
GColors *color_panel_desc (void);
|
||||
GColors *color_panel_header (void);
|
||||
GColors *color_progress (void);
|
||||
GColors *color_selected (void);
|
||||
GColors *get_color_by_item_module (GColorItem item, GModule module);
|
||||
GColors *get_color (GColorItem item);
|
||||
GColors *get_color_normal (void);
|
||||
void free_color_lists (void);
|
||||
void set_colors (int force);
|
||||
void set_normal_color (void);
|
||||
|
||||
#endif // for #ifndef COLOR_H
|
||||
+521
@@ -0,0 +1,521 @@
|
||||
/**
|
||||
* commons.c -- holds different data types
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "commons.h"
|
||||
|
||||
#include "labels.h"
|
||||
#include "settings.h"
|
||||
#include "util.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
/* processing time */
|
||||
time_t end_proc;
|
||||
time_t timestamp;
|
||||
time_t start_proc;
|
||||
|
||||
/* list of available modules/panels */
|
||||
int module_list[TOTAL_MODULES] = {[0 ... TOTAL_MODULES - 1] = -1 };
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
/* String modules to enumerated modules */
|
||||
static const GEnum enum_modules[] = {
|
||||
{"VISITORS" , VISITORS} ,
|
||||
{"REQUESTS" , REQUESTS} ,
|
||||
{"REQUESTS_STATIC" , REQUESTS_STATIC} ,
|
||||
{"NOT_FOUND" , NOT_FOUND} ,
|
||||
{"HOSTS" , HOSTS} ,
|
||||
{"OS" , OS} ,
|
||||
{"BROWSERS" , BROWSERS} ,
|
||||
{"VISIT_TIMES" , VISIT_TIMES} ,
|
||||
{"VIRTUAL_HOSTS" , VIRTUAL_HOSTS} ,
|
||||
{"REFERRERS" , REFERRERS} ,
|
||||
{"REFERRING_SITES" , REFERRING_SITES} ,
|
||||
{"KEYPHRASES" , KEYPHRASES} ,
|
||||
{"STATUS_CODES" , STATUS_CODES} ,
|
||||
{"REMOTE_USER" , REMOTE_USER} ,
|
||||
{"CACHE_STATUS" , CACHE_STATUS} ,
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
{"GEO_LOCATION" , GEO_LOCATION} ,
|
||||
{"ASN" , ASN} ,
|
||||
#endif
|
||||
{"MIME_TYPE" , MIME_TYPE} ,
|
||||
{"TLS_TYPE" , TLS_TYPE} ,
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
/* Get number of items per panel/level to parse.
|
||||
*
|
||||
* If is_sub is true, returns sub-item limits, otherwise returns panel limits.
|
||||
* The number of items per panel/level is returned. */
|
||||
static uint32_t
|
||||
get_max_choices_internal (int is_sub) {
|
||||
char *csv = NULL, *json = NULL, *html = NULL;
|
||||
int max = MAX_CHOICES;
|
||||
int rt_default = is_sub ? MAX_CHOICES_SUB_RT : MAX_CHOICES_RT;
|
||||
|
||||
/* no max choices, return defaults */
|
||||
if (conf.max_items <= 0)
|
||||
return conf.real_time_html ? rt_default : (conf.date_spec_hr == 2 ? MAX_CHOICES_MINUTE : MAX_CHOICES);
|
||||
|
||||
/* TERM */
|
||||
if (!conf.output_stdout)
|
||||
return conf.max_items > MAX_CHOICES ? MAX_CHOICES : conf.max_items;
|
||||
|
||||
/* REAL-TIME STDOUT */
|
||||
/* real time HTML, display max rt choices - always cap at MAX_CHOICES_RT */
|
||||
if (conf.real_time_html)
|
||||
return conf.max_items > MAX_CHOICES_RT ? MAX_CHOICES_RT : conf.max_items;
|
||||
|
||||
/* STDOUT */
|
||||
/* CSV - allow n amount of choices */
|
||||
if (find_output_type (&csv, "csv", 1) == 0)
|
||||
max = conf.max_items;
|
||||
/* JSON - allow n amount of choices */
|
||||
if (find_output_type (&json, "json", 1) == 0 && conf.max_items > 0)
|
||||
max = conf.max_items;
|
||||
/* HTML - takes priority on cases where multiple outputs were given. Note that
|
||||
* we check either for an .html extension or we assume not extension was passed
|
||||
* via -o and therefore we are redirecting the output to a file. */
|
||||
if (find_output_type (&html, "html", 1) == 0 || conf.output_format_idx == 0)
|
||||
max = conf.max_items;
|
||||
|
||||
free (csv);
|
||||
free (html);
|
||||
free (json);
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
/* Get number of items per panel to parse. */
|
||||
uint32_t
|
||||
get_max_choices (void) {
|
||||
return get_max_choices_internal (0);
|
||||
}
|
||||
|
||||
/* Get number of sub-items per hierarchy level to parse. */
|
||||
uint32_t
|
||||
get_max_choices_sub (void) {
|
||||
return get_max_choices_internal (1);
|
||||
}
|
||||
|
||||
/* Calculate a percentage.
|
||||
*
|
||||
* The percentage is returned. */
|
||||
float
|
||||
get_percentage (unsigned long long total, unsigned long long hit) {
|
||||
return (total == 0 ? 0 : (((float) hit) / total) * 100);
|
||||
}
|
||||
|
||||
/* Display the storage being used. */
|
||||
void
|
||||
display_storage (void) {
|
||||
fprintf (stdout, "%s\n", BUILT_WITH_DEFHASH);
|
||||
}
|
||||
|
||||
/* Display the path of the default configuration file when `-p` is not used */
|
||||
void
|
||||
display_default_config_file (void) {
|
||||
char *path = get_config_file_path ();
|
||||
|
||||
if (!path) {
|
||||
fprintf (stdout, "%s\n", ERR_NODEF_CONF_FILE);
|
||||
fprintf (stdout, "%s `-p /path/goaccess.conf`\n", ERR_NODEF_CONF_FILE_DESC);
|
||||
} else {
|
||||
fprintf (stdout, "%s\n", path);
|
||||
free (path);
|
||||
}
|
||||
}
|
||||
|
||||
/* Display the current version. */
|
||||
void
|
||||
display_version (void) {
|
||||
fprintf (stdout, "GoAccess - %s.\n", GO_VERSION);
|
||||
fprintf (stdout, "%s: %s\n", INFO_MORE_INFO, GO_WEBSITE);
|
||||
fprintf (stdout, "Copyright (C) 2009-2024 by Gerardo Orellana\n");
|
||||
fprintf (stdout, "\nBuild configure arguments:\n");
|
||||
#ifdef DEBUG
|
||||
fprintf (stdout, " --enable-debug\n");
|
||||
#endif
|
||||
#ifdef HAVE_NCURSESW_NCURSES_H
|
||||
fprintf (stdout, " --enable-utf8\n");
|
||||
#endif
|
||||
#ifdef HAVE_LIBGEOIP
|
||||
fprintf (stdout, " --enable-geoip=legacy\n");
|
||||
#endif
|
||||
#ifdef HAVE_LIBMAXMINDDB
|
||||
fprintf (stdout, " --enable-geoip=mmdb\n");
|
||||
#endif
|
||||
#ifdef WITH_GETLINE
|
||||
fprintf (stdout, " --with-getline\n");
|
||||
#endif
|
||||
#ifdef HAVE_LIBSSL
|
||||
fprintf (stdout, " --with-openssl\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Get the enumerated value given a string.
|
||||
*
|
||||
* On error, -1 is returned.
|
||||
* On success, the enumerated module value is returned. */
|
||||
int
|
||||
str2enum (const GEnum map[], int len, const char *str) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (!strcmp (str, map[i].str))
|
||||
return map[i].idx;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get the string value given an enum.
|
||||
*
|
||||
* On error, -1 is returned.
|
||||
* On success, the enumerated module value is returned. */
|
||||
const char *
|
||||
enum2str (const GEnum map[], int len, int idx) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (idx == map[i].idx)
|
||||
return map[i].str;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get the enumerated module value given a module string.
|
||||
*
|
||||
* On error, -1 is returned.
|
||||
* On success, the enumerated module value is returned. */
|
||||
int
|
||||
get_module_enum (const char *str) {
|
||||
return str2enum (enum_modules, ARRAY_SIZE (enum_modules), str);
|
||||
}
|
||||
|
||||
/* Get the module string value given a module enum value.
|
||||
*
|
||||
* On error, NULL is returned.
|
||||
* On success, the string module value is returned. */
|
||||
const char *
|
||||
get_module_str (GModule module) {
|
||||
return enum2str (enum_modules, ARRAY_SIZE (enum_modules), module);
|
||||
}
|
||||
|
||||
/* Instantiate a new GAgents structure.
|
||||
*
|
||||
* On success, the newly malloc'd structure is returned. */
|
||||
GAgents *
|
||||
new_gagents (uint32_t size) {
|
||||
GAgents *agents = xmalloc (sizeof (GAgents));
|
||||
memset (agents, 0, sizeof *agents);
|
||||
|
||||
agents->items = xcalloc (size, sizeof (GAgentItem));
|
||||
agents->size = size;
|
||||
agents->idx = 0;
|
||||
|
||||
return agents;
|
||||
}
|
||||
|
||||
/* Clean the array of agents. */
|
||||
void
|
||||
free_agents_array (GAgents *agents) {
|
||||
int i;
|
||||
|
||||
if (agents == NULL)
|
||||
return;
|
||||
|
||||
/* clean stuff up */
|
||||
for (i = 0; i < agents->idx; ++i)
|
||||
free (agents->items[i].agent);
|
||||
if (agents->items)
|
||||
free (agents->items);
|
||||
free (agents);
|
||||
}
|
||||
|
||||
/* Determine if the given date format is a timestamp.
|
||||
*
|
||||
* If not a timestamp, 0 is returned.
|
||||
* If it is a timestamp, 1 is returned. */
|
||||
int
|
||||
has_timestamp (const char *fmt) {
|
||||
if (strcmp ("%s", fmt) == 0 || strcmp ("%f", fmt) == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Determine if the given module is set to be enabled.
|
||||
*
|
||||
* If enabled, 1 is returned, else 0 is returned. */
|
||||
int
|
||||
enable_panel (GModule mod) {
|
||||
int i, module;
|
||||
|
||||
for (i = 0; i < conf.enable_panel_idx; ++i) {
|
||||
if ((module = get_module_enum (conf.enable_panels[i])) == -1)
|
||||
continue;
|
||||
if (mod == (unsigned int) module) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Determine if the given module is set to be ignored.
|
||||
*
|
||||
* If ignored, 1 is returned, else 0 is returned. */
|
||||
int
|
||||
ignore_panel (GModule mod) {
|
||||
int i, module;
|
||||
|
||||
for (i = 0; i < conf.ignore_panel_idx; ++i) {
|
||||
if ((module = get_module_enum (conf.ignore_panels[i])) == -1)
|
||||
continue;
|
||||
if (mod == (unsigned int) module) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get the number of available modules/panels.
|
||||
*
|
||||
* The number of modules available is returned. */
|
||||
uint32_t
|
||||
get_num_modules (void) {
|
||||
size_t idx = 0;
|
||||
uint32_t num = 0;
|
||||
|
||||
FOREACH_MODULE (idx, module_list) {
|
||||
num++;
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
/* Get the index from the module_list given a module.
|
||||
*
|
||||
* If the module is not within the array, -1 is returned.
|
||||
* If the module is within the array, the index is returned. */
|
||||
int
|
||||
get_module_index (int module) {
|
||||
size_t idx = 0;
|
||||
|
||||
FOREACH_MODULE (idx, module_list) {
|
||||
if (module_list[idx] == module)
|
||||
return idx;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Remove the given module from the module_list array.
|
||||
*
|
||||
* If the module is not within the array, 1 is returned.
|
||||
* If the module is within the array, it is removed from the array and
|
||||
* 0 is returned. */
|
||||
int
|
||||
remove_module (GModule module) {
|
||||
int idx = get_module_index (module);
|
||||
if (idx == -1)
|
||||
return 1;
|
||||
|
||||
if (idx < TOTAL_MODULES - 1)
|
||||
memmove (&module_list[idx], &module_list[idx + 1],
|
||||
((TOTAL_MODULES - 1) - idx) * sizeof (module_list[0]));
|
||||
module_list[TOTAL_MODULES - 1] = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find the next module given the current module.
|
||||
*
|
||||
* The next available module in the array is returned. */
|
||||
int
|
||||
get_next_module (GModule module) {
|
||||
int next = get_module_index (module) + 1;
|
||||
|
||||
if (next == TOTAL_MODULES || module_list[next] == -1)
|
||||
return module_list[0];
|
||||
|
||||
return module_list[next];
|
||||
}
|
||||
|
||||
/* Find the previous module given the current module.
|
||||
*
|
||||
* The previous available module in the array is returned. */
|
||||
int
|
||||
get_prev_module (GModule module) {
|
||||
int i;
|
||||
int next = get_module_index (module) - 1;
|
||||
|
||||
if (next >= 0 && module_list[next] != -1)
|
||||
return module_list[next];
|
||||
|
||||
for (i = TOTAL_MODULES - 1; i >= 0; i--) {
|
||||
if (module_list[i] != -1) {
|
||||
return module_list[i];
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Perform some additional tasks to panels before they are being
|
||||
* parsed.
|
||||
*
|
||||
* Note: This overwrites --enable-panel since it assumes there's
|
||||
* truly nothing to do with the panel */
|
||||
void
|
||||
verify_panels (void) {
|
||||
int ignore_panel_idx = conf.ignore_panel_idx;
|
||||
|
||||
/* Remove virtual host panel if no '%v' within log format */
|
||||
if (!conf.log_format)
|
||||
return;
|
||||
|
||||
if (!strstr (conf.log_format, "%v") && ignore_panel_idx < TOTAL_MODULES && !conf.fname_as_vhost) {
|
||||
if (str_inarray ("VIRTUAL_HOSTS", conf.ignore_panels, ignore_panel_idx) < 0)
|
||||
remove_module (VIRTUAL_HOSTS);
|
||||
}
|
||||
if (!strstr (conf.log_format, "%e") && ignore_panel_idx < TOTAL_MODULES) {
|
||||
if (str_inarray ("REMOTE_USER", conf.ignore_panels, ignore_panel_idx) < 0)
|
||||
remove_module (REMOTE_USER);
|
||||
}
|
||||
if (!strstr (conf.log_format, "%C") && ignore_panel_idx < TOTAL_MODULES) {
|
||||
if (str_inarray ("CACHE_STATUS", conf.ignore_panels, ignore_panel_idx) < 0)
|
||||
remove_module (CACHE_STATUS);
|
||||
}
|
||||
if (!strstr (conf.log_format, "%M") && ignore_panel_idx < TOTAL_MODULES) {
|
||||
if (str_inarray ("MIME_TYPE", conf.ignore_panels, ignore_panel_idx) < 0)
|
||||
remove_module (MIME_TYPE);
|
||||
}
|
||||
if (!strstr (conf.log_format, "%K") && ignore_panel_idx < TOTAL_MODULES) {
|
||||
if (str_inarray ("TLS_TYPE", conf.ignore_panels, ignore_panel_idx) < 0)
|
||||
remove_module (TLS_TYPE);
|
||||
}
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
#ifdef HAVE_LIBMAXMINDDB
|
||||
if (!conf.geoip_db_idx && ignore_panel_idx < TOTAL_MODULES) {
|
||||
if (str_inarray ("GEO_LOCATION", conf.ignore_panels, ignore_panel_idx) < 0)
|
||||
remove_module (GEO_LOCATION);
|
||||
}
|
||||
if (!conf.geoip_db_idx && ignore_panel_idx < TOTAL_MODULES) {
|
||||
if (str_inarray ("ASN", conf.ignore_panels, ignore_panel_idx) < 0)
|
||||
remove_module (ASN);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Build an array of available modules (ignores listed panels).
|
||||
*
|
||||
* If there are no modules enabled, 0 is returned.
|
||||
* On success, the first enabled module is returned. */
|
||||
int
|
||||
init_modules (void) {
|
||||
GModule module;
|
||||
int i;
|
||||
|
||||
/* init - terminating with -1 */
|
||||
for (module = 0; module < TOTAL_MODULES; ++module)
|
||||
module_list[module] = -1;
|
||||
|
||||
for (i = 0, module = 0; module < TOTAL_MODULES; ++module) {
|
||||
if (!ignore_panel (module) || enable_panel (module)) {
|
||||
module_list[i++] = module;
|
||||
}
|
||||
}
|
||||
|
||||
return module_list[0] > -1 ? module_list[0] : 0;
|
||||
}
|
||||
|
||||
/* Get the logs size.
|
||||
*
|
||||
* If log was piped (from stdin), 0 is returned.
|
||||
* On success, it adds up all log sizes and its value is returned.
|
||||
* if --log-size was specified, it will be returned explicitly */
|
||||
intmax_t
|
||||
get_log_sizes (void) {
|
||||
int i;
|
||||
off_t size = 0;
|
||||
|
||||
/* --log-size */
|
||||
if (conf.log_size > 0)
|
||||
return (intmax_t) conf.log_size;
|
||||
|
||||
for (i = 0; i < conf.filenames_idx; ++i) {
|
||||
if (conf.filenames[i][0] == '-' && conf.filenames[i][1] == '\0')
|
||||
size += 0;
|
||||
else
|
||||
size += file_size (conf.filenames[i]);
|
||||
}
|
||||
|
||||
return (intmax_t) size;
|
||||
}
|
||||
|
||||
/* Get the log sources used.
|
||||
*
|
||||
* On success, a newly malloc'd string containing the log source either
|
||||
* from filename(s) and/or STDIN is returned. */
|
||||
char *
|
||||
get_log_source_str (int max_len) {
|
||||
char *str = xstrdup ("");
|
||||
int i, len = 0;
|
||||
|
||||
for (i = 0; i < conf.filenames_idx; ++i) {
|
||||
if (conf.filenames[i][0] == '-' && conf.filenames[i][1] == '\0')
|
||||
append_str (&str, "STDIN");
|
||||
else
|
||||
append_str (&str, conf.filenames[i]);
|
||||
if (i != conf.filenames_idx - 1)
|
||||
append_str (&str, "; ");
|
||||
}
|
||||
|
||||
len = strlen (str);
|
||||
if (max_len > 0 && len > 0 && len > max_len) {
|
||||
str[max_len - 3] = 0;
|
||||
append_str (&str, "...");
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifndef COMMONS_H_INCLUDED
|
||||
#define COMMONS_H_INCLUDED
|
||||
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
#include "gslist.h"
|
||||
|
||||
/* Remove the __attribute__ stuff when the compiler is not GCC. */
|
||||
#if !__GNUC__
|
||||
#define __attribute__(x) /**/
|
||||
#endif
|
||||
#define GO_UNUSED __attribute__((unused))
|
||||
#define GO_VERSION "1.10.2"
|
||||
#define GO_WEBSITE "https://goaccess.io/"
|
||||
extern struct tm now_tm;
|
||||
|
||||
/* common char array buffer size */
|
||||
#define INIT_BUF_SIZE 1024
|
||||
|
||||
/* total number of modules */
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
#define TOTAL_MODULES 19
|
||||
#else
|
||||
#define TOTAL_MODULES 17
|
||||
#endif
|
||||
|
||||
/* maximum number of items within a panel */
|
||||
#define MAX_CHOICES 366
|
||||
/* real-time */
|
||||
#define MAX_CHOICES_RT 50
|
||||
/* real-time sub-items */
|
||||
#define MAX_CHOICES_SUB_RT 10
|
||||
/* max default items when date-spec = min */
|
||||
#define MAX_CHOICES_MINUTE 1440 /* 24hrs */
|
||||
|
||||
/* date and time length - e.g., 2016/12/12 12:12:12 -0600 */
|
||||
#define DATE_TIME 25 + 1
|
||||
/* date length - e.g., 2016/12/12 */
|
||||
#define DATE_LEN 10 + 1
|
||||
/* date length - e.g., 12:12:12 */
|
||||
#define TIME_LEN 8 + 1
|
||||
/* hour + ':' + min length - e.g., 12:12 */
|
||||
#define HRMI_LEN 4 + 1 + 1
|
||||
|
||||
#define YR_FMT "%Y"
|
||||
#define MO_FMT "%M"
|
||||
#define DT_FMT "%d"
|
||||
|
||||
/* maximum protocol string length */
|
||||
#define REQ_PROTO_LEN 9
|
||||
|
||||
#define IGNORE_LEVEL_PANEL 1
|
||||
#define IGNORE_LEVEL_REQ 2
|
||||
|
||||
/* Type of IP */
|
||||
typedef enum {
|
||||
TYPE_IPINV,
|
||||
TYPE_IPV4,
|
||||
TYPE_IPV6
|
||||
} GTypeIP;
|
||||
|
||||
/* Type of Modules */
|
||||
typedef enum MODULES {
|
||||
VISITORS,
|
||||
REQUESTS,
|
||||
REQUESTS_STATIC,
|
||||
NOT_FOUND,
|
||||
HOSTS,
|
||||
OS,
|
||||
BROWSERS,
|
||||
VISIT_TIMES,
|
||||
VIRTUAL_HOSTS,
|
||||
REFERRERS,
|
||||
REFERRING_SITES,
|
||||
KEYPHRASES,
|
||||
STATUS_CODES,
|
||||
REMOTE_USER,
|
||||
CACHE_STATUS,
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
GEO_LOCATION,
|
||||
ASN,
|
||||
#endif
|
||||
MIME_TYPE,
|
||||
TLS_TYPE,
|
||||
} GModule;
|
||||
|
||||
/* Total number of storage metrics (GSMetric) */
|
||||
#define GSMTRC_TOTAL 19
|
||||
|
||||
/* Enumerated Storage Metrics */
|
||||
typedef enum GSMetric_ {
|
||||
MTRC_KEYMAP,
|
||||
MTRC_ROOTMAP,
|
||||
MTRC_DATAMAP,
|
||||
MTRC_UNIQMAP,
|
||||
MTRC_ROOT,
|
||||
MTRC_HITS,
|
||||
MTRC_VISITORS,
|
||||
MTRC_BW,
|
||||
MTRC_CUMTS,
|
||||
MTRC_MAXTS,
|
||||
MTRC_METHODS,
|
||||
MTRC_PROTOCOLS,
|
||||
MTRC_AGENTS,
|
||||
MTRC_METADATA,
|
||||
MTRC_UNIQUE_KEYS,
|
||||
MTRC_AGENT_KEYS,
|
||||
MTRC_AGENT_VALS,
|
||||
MTRC_CNT_VALID,
|
||||
MTRC_CNT_BW,
|
||||
} GSMetric;
|
||||
|
||||
/* Metric totals. These are metrics that have a percent value and are
|
||||
* calculated values. */
|
||||
typedef struct GPercTotals_ {
|
||||
uint32_t hits; /* total valid hits */
|
||||
uint32_t visitors; /* total visitors */
|
||||
uint64_t bw; /* total bandwidth */
|
||||
} GPercTotals;
|
||||
|
||||
/* Metrics within GHolder or GDashData */
|
||||
typedef struct GMetrics {
|
||||
/* metric id can be used to identify
|
||||
* a specific data field */
|
||||
uint8_t id;
|
||||
char *data;
|
||||
char *method;
|
||||
char *protocol;
|
||||
|
||||
float hits_perc;
|
||||
float visitors_perc;
|
||||
float bw_perc;
|
||||
|
||||
uint64_t hits;
|
||||
uint64_t visitors;
|
||||
uint64_t nbw;
|
||||
double lat;
|
||||
double lng;
|
||||
|
||||
/* holder has a numeric value, while
|
||||
* dashboard has a displayable string value */
|
||||
union {
|
||||
char *sts;
|
||||
uint64_t nts;
|
||||
} avgts;
|
||||
|
||||
/* holder has a numeric value, while
|
||||
* dashboard has a displayable string value */
|
||||
union {
|
||||
char *sts;
|
||||
uint64_t nts;
|
||||
} cumts;
|
||||
|
||||
/* holder has a numeric value, while
|
||||
* dashboard has a displayable string value */
|
||||
union {
|
||||
char *sts;
|
||||
uint64_t nts;
|
||||
} maxts;
|
||||
} GMetrics;
|
||||
|
||||
/* Forward declaration for recursive sub-items */
|
||||
typedef struct GSubList_ GSubList;
|
||||
|
||||
/* Holder sub item */
|
||||
typedef struct GSubItem_ {
|
||||
GModule module;
|
||||
GMetrics *metrics;
|
||||
GSubList *sub_list;
|
||||
struct GSubItem_ *prev;
|
||||
struct GSubItem_ *next;
|
||||
} GSubItem;
|
||||
|
||||
/* Double linked-list of sub items */
|
||||
struct GSubList_ {
|
||||
uint32_t size;
|
||||
struct GSubItem_ *head;
|
||||
struct GSubItem_ *tail;
|
||||
};
|
||||
|
||||
/* Holder item */
|
||||
typedef struct GHolderItem_ {
|
||||
GSubList *sub_list;
|
||||
GMetrics *metrics;
|
||||
} GHolderItem;
|
||||
|
||||
/* Holder of GRawData */
|
||||
typedef struct GHolder_ {
|
||||
GHolderItem *items; /* holder items */
|
||||
GModule module; /* current module */
|
||||
uint32_t idx; /* holder index */
|
||||
uint32_t holder_size; /* number of allocated items */
|
||||
uint32_t ht_size; /* size of the hash table/store */
|
||||
uint32_t sub_items_size; /* number of sub items */
|
||||
uint32_t max_choices; /* max items at root level */
|
||||
uint32_t max_choices_sub; /* max items at sub-item levels */
|
||||
} GHolder;
|
||||
|
||||
/* Enum-to-string */
|
||||
typedef struct GEnum_ {
|
||||
const char *str;
|
||||
int idx;
|
||||
} GEnum;
|
||||
|
||||
/* A metric can contain a root/data/uniq node id */
|
||||
typedef struct GDataMap_ {
|
||||
int data;
|
||||
int root;
|
||||
} GDataMap;
|
||||
|
||||
typedef struct GAgentItem_ {
|
||||
char *agent;
|
||||
} GAgentItem;
|
||||
|
||||
typedef struct GAgents_ {
|
||||
int size;
|
||||
int idx;
|
||||
struct GAgentItem_ *items;
|
||||
} GAgents;
|
||||
|
||||
#define FOREACH_MODULE(item, array) \
|
||||
for (; (item < ARRAY_SIZE(array)) && array[item] != -1; ++item)
|
||||
|
||||
/* Processing time */
|
||||
extern time_t end_proc;
|
||||
extern time_t timestamp;
|
||||
extern time_t start_proc;
|
||||
|
||||
/* list of available modules/panels */
|
||||
extern int module_list[TOTAL_MODULES];
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
GAgents *new_gagents (uint32_t size);
|
||||
void free_agents_array (GAgents *agents);
|
||||
|
||||
const char *enum2str (const GEnum map[], int len, int idx);
|
||||
const char *get_module_str (GModule module);
|
||||
float get_percentage (unsigned long long total, unsigned long long hit);
|
||||
uint32_t get_max_choices (void);
|
||||
uint32_t get_max_choices_sub (void);
|
||||
int get_module_enum (const char *str);
|
||||
int has_timestamp (const char *fmt);
|
||||
int str2enum (const GEnum map[], int len, const char *str);
|
||||
|
||||
int enable_panel (GModule mod);
|
||||
int get_module_index (int module);
|
||||
int get_next_module (GModule module);
|
||||
int get_prev_module (GModule module);
|
||||
int ignore_panel (GModule mod);
|
||||
int init_modules (void);
|
||||
int remove_module(GModule module);
|
||||
uint32_t get_num_modules (void);
|
||||
void verify_panels (void);
|
||||
|
||||
char *get_log_source_str (int max_len);
|
||||
intmax_t get_log_sizes (void);
|
||||
|
||||
void display_default_config_file (void);
|
||||
void display_storage (void);
|
||||
void display_version (void);
|
||||
/* *INDENT-ON* */
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,326 @@
|
||||
/**
|
||||
* output.c -- output csv to the standard output stream
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#define _LARGEFILE_SOURCE
|
||||
#define _LARGEFILE64_SOURCE
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "csv.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "gkhash.h"
|
||||
#include "ui.h"
|
||||
#include "util.h"
|
||||
|
||||
struct tm now_tm;
|
||||
|
||||
/* Panel output */
|
||||
typedef struct GPanel_ {
|
||||
GModule module;
|
||||
void (*render) (FILE * fp, GHolder * h, GPercTotals totals);
|
||||
} GPanel;
|
||||
|
||||
static void print_csv_data (FILE * fp, GHolder * h, GPercTotals totals);
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
/* A function pointer for each panel */
|
||||
static const GPanel paneling[] = {
|
||||
{VISITORS , print_csv_data} ,
|
||||
{REQUESTS , print_csv_data} ,
|
||||
{REQUESTS_STATIC , print_csv_data} ,
|
||||
{NOT_FOUND , print_csv_data} ,
|
||||
{HOSTS , print_csv_data} ,
|
||||
{OS , print_csv_data} ,
|
||||
{BROWSERS , print_csv_data} ,
|
||||
{VISIT_TIMES , print_csv_data} ,
|
||||
{VIRTUAL_HOSTS , print_csv_data} ,
|
||||
{REFERRERS , print_csv_data} ,
|
||||
{REFERRING_SITES , print_csv_data} ,
|
||||
{KEYPHRASES , print_csv_data} ,
|
||||
{STATUS_CODES , print_csv_data} ,
|
||||
{REMOTE_USER , print_csv_data} ,
|
||||
{CACHE_STATUS , print_csv_data} ,
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
{GEO_LOCATION , print_csv_data} ,
|
||||
{ASN , print_csv_data} ,
|
||||
#endif
|
||||
{MIME_TYPE , print_csv_data} ,
|
||||
{TLS_TYPE , print_csv_data} ,
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
/* Get a panel from the GPanel structure given a module.
|
||||
*
|
||||
* On error, or if not found, NULL is returned.
|
||||
* On success, the panel value is returned. */
|
||||
static const GPanel *
|
||||
panel_lookup (GModule module) {
|
||||
int i, num_panels = ARRAY_SIZE (paneling);
|
||||
|
||||
for (i = 0; i < num_panels; i++) {
|
||||
if (paneling[i].module == module)
|
||||
return &paneling[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Iterate over the string and escape CSV output. */
|
||||
static void
|
||||
escape_cvs_output (FILE *fp, char *s) {
|
||||
while (*s) {
|
||||
switch (*s) {
|
||||
case '"':
|
||||
fprintf (fp, "\"\"");
|
||||
break;
|
||||
default:
|
||||
fputc (*s, fp);
|
||||
break;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Output metrics.
|
||||
*
|
||||
* On success, outputs item value. */
|
||||
static void
|
||||
print_csv_metric_block (FILE *fp, GMetrics *nmetrics) {
|
||||
/* basic metrics */
|
||||
fprintf (fp, "\"%" PRIu64 "\",", nmetrics->hits);
|
||||
fprintf (fp, "\"%4.2f%%\",", nmetrics->hits_perc);
|
||||
fprintf (fp, "\"%" PRIu64 "\",", nmetrics->visitors);
|
||||
fprintf (fp, "\"%4.2f%%\",", nmetrics->visitors_perc);
|
||||
|
||||
/* bandwidth */
|
||||
if (conf.bandwidth) {
|
||||
fprintf (fp, "\"%" PRIu64 "\",", nmetrics->nbw);
|
||||
fprintf (fp, "\"%4.2f%%\",", nmetrics->bw_perc);
|
||||
}
|
||||
|
||||
/* time served metrics */
|
||||
if (conf.serve_usecs) {
|
||||
fprintf (fp, "\"%" PRIu64 "\",", nmetrics->avgts.nts);
|
||||
fprintf (fp, "\"%" PRIu64 "\",", nmetrics->cumts.nts);
|
||||
fprintf (fp, "\"%" PRIu64 "\",", nmetrics->maxts.nts);
|
||||
}
|
||||
|
||||
/* request method */
|
||||
if (conf.append_method && nmetrics->method)
|
||||
fprintf (fp, "\"%s\"", nmetrics->method);
|
||||
fprintf (fp, ",");
|
||||
|
||||
/* request protocol */
|
||||
if (conf.append_protocol && nmetrics->protocol)
|
||||
fprintf (fp, "\"%s\"", nmetrics->protocol);
|
||||
fprintf (fp, ",");
|
||||
|
||||
/* data field */
|
||||
fprintf (fp, "\"");
|
||||
escape_cvs_output (fp, nmetrics->data);
|
||||
fprintf (fp, "\"\r\n");
|
||||
}
|
||||
|
||||
/* Output a sublist (double linked-list) items for a particular parent node.
|
||||
*
|
||||
* On error, it exits early.
|
||||
* On success, outputs item value. */
|
||||
static void
|
||||
print_csv_sub_items (FILE *fp, GSubList *sub_list, GModule module, int parent_idx,
|
||||
GPercTotals totals, int depth) {
|
||||
GMetrics *nmetrics;
|
||||
GSubItem *iter;
|
||||
|
||||
uint32_t i = 0;
|
||||
|
||||
if (sub_list == NULL)
|
||||
return;
|
||||
|
||||
for (iter = sub_list->head; iter; iter = iter->next, i++) {
|
||||
set_data_metrics (iter->metrics, &nmetrics, totals);
|
||||
|
||||
fprintf (fp, "\"%u\",", i); /* idx */
|
||||
fprintf (fp, "\"%d\",", parent_idx); /* parent idx */
|
||||
fprintf (fp, "\"%s\",", module_to_id (module));
|
||||
fprintf (fp, "\"%d\",", depth); /* depth */
|
||||
|
||||
/* output metrics */
|
||||
print_csv_metric_block (fp, nmetrics);
|
||||
|
||||
/* recurse into nested sub-items */
|
||||
if (iter->sub_list != NULL)
|
||||
print_csv_sub_items (fp, iter->sub_list, module, i, totals, depth + 1);
|
||||
|
||||
free (nmetrics);
|
||||
}
|
||||
}
|
||||
|
||||
/* Output first-level items.
|
||||
*
|
||||
* On success, outputs item value. */
|
||||
static void
|
||||
print_csv_data (FILE *fp, GHolder *h, GPercTotals totals) {
|
||||
GMetrics *nmetrics;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < h->idx; i++) {
|
||||
set_data_metrics (h->items[i].metrics, &nmetrics, totals);
|
||||
|
||||
fprintf (fp, "\"%u\",", i); /* idx */
|
||||
fprintf (fp, ","); /* no parent */
|
||||
fprintf (fp, "\"%s\",", module_to_id (h->module));
|
||||
|
||||
/* output metrics */
|
||||
print_csv_metric_block (fp, nmetrics);
|
||||
|
||||
if (h->sub_items_size)
|
||||
print_csv_sub_items (fp, h->items[i].sub_list, h->module, i, totals, 1);
|
||||
|
||||
free (nmetrics);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
||||
/* Output general statistics information. */
|
||||
static void
|
||||
print_csv_summary (FILE *fp) {
|
||||
char now[DATE_TIME];
|
||||
char *source = NULL;
|
||||
const char *fmt;
|
||||
int i = 0;
|
||||
uint64_t total = 0;
|
||||
uint32_t t = 0;
|
||||
|
||||
generate_time ();
|
||||
strftime (now, DATE_TIME, "%Y-%m-%d %H:%M:%S %z", &now_tm);
|
||||
|
||||
/* generated date time */
|
||||
fmt = "\"%d\",,\"%s\",,,,,,,,\"%s\",\"%s\"\r\n";
|
||||
fprintf (fp, fmt, i++, GENER_ID, now, OVERALL_DATETIME);
|
||||
|
||||
/* total requests */
|
||||
fmt = "\"%d\",,\"%s\",,,,,,,,\"%" PRIu64 "\",\"%s\"\r\n";
|
||||
total = ht_get_processed ();
|
||||
fprintf (fp, fmt, i++, GENER_ID, total, OVERALL_REQ);
|
||||
|
||||
/* valid requests */
|
||||
fmt = "\"%d\",,\"%s\",,,,,,,,\"%" PRIu64 "\",\"%s\"\r\n";
|
||||
total = ht_sum_valid ();
|
||||
fprintf (fp, fmt, i++, GENER_ID, total, OVERALL_VALID);
|
||||
|
||||
/* invalid requests */
|
||||
total = ht_get_invalid ();
|
||||
fprintf (fp, fmt, i++, GENER_ID, total, OVERALL_FAILED);
|
||||
|
||||
/* generated time */
|
||||
fmt = "\"%d\",,\"%s\",,,,,,,,\"%u\",\"%s\"\r\n";
|
||||
t = ht_get_processing_time ();
|
||||
fprintf (fp, fmt, i++, GENER_ID, t, OVERALL_GENTIME);
|
||||
|
||||
/* visitors */
|
||||
fmt = "\"%d\",,\"%s\",,,,,,,,\"%" PRIu64 "\",\"%s\"\r\n";
|
||||
total = ht_get_size_uniqmap (VISITORS);
|
||||
fprintf (fp, fmt, i++, GENER_ID, total, OVERALL_VISITORS);
|
||||
|
||||
/* files */
|
||||
total = ht_get_size_datamap (REQUESTS);
|
||||
fprintf (fp, fmt, i++, GENER_ID, total, OVERALL_FILES);
|
||||
|
||||
/* excluded hits */
|
||||
total = ht_get_excluded_ips ();
|
||||
fprintf (fp, fmt, i++, GENER_ID, total, OVERALL_EXCL_HITS);
|
||||
|
||||
/* referrers */
|
||||
total = ht_get_size_datamap (REFERRERS);
|
||||
fprintf (fp, fmt, i++, GENER_ID, total, OVERALL_REF);
|
||||
|
||||
/* not found */
|
||||
total = ht_get_size_datamap (NOT_FOUND);
|
||||
fprintf (fp, fmt, i++, GENER_ID, total, OVERALL_NOTFOUND);
|
||||
|
||||
/* static files */
|
||||
total = ht_get_size_datamap (REQUESTS_STATIC);
|
||||
fprintf (fp, fmt, i++, GENER_ID, total, OVERALL_STATIC);
|
||||
|
||||
/* log size */
|
||||
fmt = "\"%d\",,\"%s\",,,,,,,,\"%jd\",\"%s\"\r\n";
|
||||
fprintf (fp, fmt, i++, GENER_ID, (intmax_t) get_log_sizes (), OVERALL_LOGSIZE);
|
||||
|
||||
/* bandwidth */
|
||||
fmt = "\"%d\",,\"%s\",,,,,,,,\"%" PRIu64 "\",\"%s\"\r\n";
|
||||
fprintf (fp, fmt, i++, GENER_ID, ht_sum_bw (), OVERALL_BANDWIDTH);
|
||||
|
||||
/* log path */
|
||||
source = get_log_source_str (0);
|
||||
fmt = "\"%d\",,\"%s\",,,,,,,,\"%s\",\"%s\"\r\n";
|
||||
fprintf (fp, fmt, i++, GENER_ID, source, OVERALL_LOG);
|
||||
free (source);
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic warning "-Wformat-nonliteral"
|
||||
|
||||
/* Entry point to generate a csv report writing it to the fp */
|
||||
void
|
||||
output_csv (GHolder *holder, const char *filename) {
|
||||
GModule module;
|
||||
GPercTotals totals;
|
||||
const GPanel *panel = NULL;
|
||||
size_t idx = 0;
|
||||
FILE *fp;
|
||||
|
||||
fp = (filename != NULL) ? fopen (filename, "w") : stdout;
|
||||
if (!fp)
|
||||
FATAL ("Unable to open CSV file: %s.", strerror (errno));
|
||||
|
||||
if (!conf.no_csv_summary)
|
||||
print_csv_summary (fp);
|
||||
|
||||
set_module_totals (&totals);
|
||||
|
||||
FOREACH_MODULE (idx, module_list) {
|
||||
module = module_list[idx];
|
||||
|
||||
if (!(panel = panel_lookup (module))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
panel->render (fp, holder + module, totals);
|
||||
}
|
||||
|
||||
fclose (fp);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifndef CSV_H_INCLUDED
|
||||
#define CSV_H_INCLUDED
|
||||
|
||||
#include <stdio.h>
|
||||
#include "parser.h"
|
||||
#include "settings.h"
|
||||
|
||||
void output_csv (GHolder * holder, const char *filename);
|
||||
|
||||
#endif
|
||||
+1024
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* dialogs.h -- UI dialog windows
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef DIALOGS_H_INCLUDED
|
||||
#define DIALOGS_H_INCLUDED
|
||||
|
||||
#include "ui.h"
|
||||
#include "sort.h"
|
||||
|
||||
void load_help_popup (WINDOW * main_win);
|
||||
void load_sort_win (WINDOW * main_win, GModule module, GSort * sort);
|
||||
void load_schemes_win (WINDOW * main_win);
|
||||
int render_confdlg (Logs * logs, GSpinner * spinner);
|
||||
GAgents *load_host_agents (const char *addr);
|
||||
void load_agent_list (WINDOW * main_win, char *addr);
|
||||
void load_panels_win (WINDOW * main_win);
|
||||
|
||||
#endif
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* error.c -- error handling
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#if defined(__GLIBC__)
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "labels.h"
|
||||
#include "parser.h"
|
||||
|
||||
static FILE *access_log;
|
||||
static FILE *log_file;
|
||||
static FILE *log_invalid;
|
||||
static FILE *log_unknowns;
|
||||
static Logs *log_data;
|
||||
static struct sigaction old_sigsegv_handler;
|
||||
|
||||
/* Open a debug file whose name is specified in the given path. */
|
||||
void
|
||||
dbg_log_open (const char *path) {
|
||||
if (path != NULL) {
|
||||
log_file = fopen (path, "w");
|
||||
if (log_file == NULL)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the debug file. */
|
||||
void
|
||||
dbg_log_close (void) {
|
||||
if (log_file != NULL)
|
||||
fclose (log_file);
|
||||
}
|
||||
|
||||
/* Open the invalid requests log file whose name is specified in the
|
||||
* given path. */
|
||||
void
|
||||
invalid_log_open (const char *path) {
|
||||
if (path != NULL) {
|
||||
log_invalid = fopen (path, "w");
|
||||
if (log_invalid == NULL)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the invalid requests log file. */
|
||||
void
|
||||
invalid_log_close (void) {
|
||||
if (log_invalid != NULL)
|
||||
fclose (log_invalid);
|
||||
}
|
||||
|
||||
/* Open the unknowns log file whose name is specified in the
|
||||
* given path. */
|
||||
void
|
||||
unknowns_log_open (const char *path) {
|
||||
if (path != NULL) {
|
||||
log_unknowns = fopen (path, "w");
|
||||
if (log_unknowns == NULL)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the unknowns log file. */
|
||||
void
|
||||
unknowns_log_close (void) {
|
||||
if (log_unknowns != NULL)
|
||||
fclose (log_unknowns);
|
||||
}
|
||||
|
||||
/* Set current overall parsed log data. */
|
||||
void
|
||||
set_signal_data (void *p) {
|
||||
log_data = p;
|
||||
}
|
||||
|
||||
/* Open the WebSocket access log file whose name is specified in the
|
||||
* given path. */
|
||||
int
|
||||
access_log_open (const char *path) {
|
||||
if (path == NULL)
|
||||
return 0;
|
||||
|
||||
access_log = fopen (path, "a");
|
||||
|
||||
if (access_log == NULL)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Close the WebSocket access log file. */
|
||||
void
|
||||
access_log_close (void) {
|
||||
if (access_log != NULL)
|
||||
fclose (access_log);
|
||||
}
|
||||
|
||||
/* Set up sigsegv handler. */
|
||||
void
|
||||
setup_sigsegv_handler (void) {
|
||||
struct sigaction act;
|
||||
|
||||
sigemptyset (&act.sa_mask);
|
||||
act.sa_flags = (int) SA_RESETHAND;
|
||||
act.sa_handler = sigsegv_handler;
|
||||
|
||||
sigaction (SIGSEGV, &act, &old_sigsegv_handler);
|
||||
}
|
||||
|
||||
static void
|
||||
dump_struct_data (FILE *fp, GLog *glog, int pid) {
|
||||
fprintf (fp, "==%d== FILE: %s\n", pid, glog->props.filename);
|
||||
fprintf (fp, "==%d== Line number: %" PRIu64 "\n", pid, glog->processed);
|
||||
fprintf (fp, "==%d== Invalid data: %" PRIu64 "\n", pid, glog->invalid);
|
||||
fprintf (fp, "==%d== Piping: %d\n", pid, glog->piping);
|
||||
fprintf (fp, "==%d==\n", pid);
|
||||
}
|
||||
|
||||
/* Dump to the standard output the values of the overall parsed log
|
||||
* data. */
|
||||
static void
|
||||
dump_struct (FILE *fp) {
|
||||
int pid = getpid (), i;
|
||||
|
||||
if (!log_data)
|
||||
return;
|
||||
|
||||
fprintf (fp, "==%d== VALUES AT CRASH POINT\n", pid);
|
||||
fprintf (fp, "==%d==\n", pid);
|
||||
|
||||
for (i = 0; i < log_data->size; ++i)
|
||||
dump_struct_data (fp, &log_data->glog[i], pid);
|
||||
|
||||
}
|
||||
|
||||
/* Custom SIGSEGV handler. */
|
||||
void
|
||||
sigsegv_handler (int sig) {
|
||||
FILE *fp = stderr;
|
||||
int pid = getpid ();
|
||||
|
||||
#if defined(__GLIBC__)
|
||||
char **messages;
|
||||
size_t size, i;
|
||||
void *trace_stack[TRACE_SIZE];
|
||||
#endif
|
||||
|
||||
(void) endwin ();
|
||||
fprintf (fp, "\n");
|
||||
fprintf (fp, "==%d== GoAccess %s crashed by Sig %d\n", pid, GO_VERSION, sig);
|
||||
fprintf (fp, "==%d==\n", pid);
|
||||
|
||||
dump_struct (fp);
|
||||
|
||||
#if defined(__GLIBC__)
|
||||
size = backtrace (trace_stack, TRACE_SIZE);
|
||||
messages = backtrace_symbols (trace_stack, size);
|
||||
|
||||
fprintf (fp, "==%d== STACK TRACE:\n", pid);
|
||||
fprintf (fp, "==%d==\n", pid);
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
fprintf (fp, "==%d== %zu %s\n", pid, i, messages[i]);
|
||||
#endif
|
||||
|
||||
fprintf (fp, "==%d==\n", pid);
|
||||
fprintf (fp, "==%d== %s:\n", pid, ERR_PLEASE_REPORT);
|
||||
fprintf (fp, "==%d== https://github.com/allinurl/goaccess/issues\n\n", pid);
|
||||
fflush (fp);
|
||||
|
||||
/* Call old sigsegv handler; may be default exit or third party one (e.g. ASAN) */
|
||||
sigaction (SIGSEGV, &old_sigsegv_handler, NULL);
|
||||
}
|
||||
|
||||
/* Write formatted debug log data to the logfile. */
|
||||
void
|
||||
dbg_fprintf (const char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
if (!log_file)
|
||||
return;
|
||||
|
||||
va_start (args, fmt);
|
||||
vfprintf (log_file, fmt, args);
|
||||
fflush (log_file);
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
/* Write formatted invalid requests log data to the logfile. */
|
||||
void
|
||||
invalid_fprintf (const char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
if (!log_invalid)
|
||||
return;
|
||||
|
||||
va_start (args, fmt);
|
||||
vfprintf (log_invalid, fmt, args);
|
||||
fflush (log_invalid);
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
/* Write formatted unknown browsers/OSs log data to the logfile. */
|
||||
void
|
||||
unknowns_fprintf (const char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
if (!log_unknowns)
|
||||
return;
|
||||
|
||||
va_start (args, fmt);
|
||||
vfprintf (log_unknowns, fmt, args);
|
||||
fflush (log_unknowns);
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
/* Debug output */
|
||||
void
|
||||
dbg_printf (const char *fmt, ...) {
|
||||
va_list args;
|
||||
va_start (args, fmt);
|
||||
vfprintf (stderr, fmt, args);
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
/* Write formatted access log data to the logfile. */
|
||||
void
|
||||
access_fprintf (const char *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
if (!access_log)
|
||||
return;
|
||||
|
||||
va_start (args, fmt);
|
||||
vfprintf (access_log, fmt, args);
|
||||
fflush (access_log);
|
||||
va_end (args);
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef ERROR_H_INCLUDED
|
||||
#define ERROR_H_INCLUDED
|
||||
|
||||
#ifndef COMMONS
|
||||
#include "commons.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NCURSESW_NCURSES_H
|
||||
#include <ncursesw/ncurses.h>
|
||||
#elif HAVE_NCURSES_NCURSES_H
|
||||
#include <ncurses/ncurses.h>
|
||||
#elif HAVE_NCURSES_H
|
||||
#include <ncurses.h>
|
||||
#elif HAVE_CURSES_H
|
||||
#include <curses.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include "settings.h"
|
||||
|
||||
#define TRACE_SIZE 128
|
||||
|
||||
#define FATAL(fmt, ...) do { \
|
||||
(void) endwin (); \
|
||||
fprintf (stderr, "\nGoAccess - version %s - %s %s\n", GO_VERSION, __DATE__, __TIME__); \
|
||||
fprintf (stderr, "Config file: %s\n", conf.iconfigfile ?: NO_CONFIG_FILE); \
|
||||
fprintf (stderr, "\nFatal error has occurred"); \
|
||||
fprintf (stderr, "\nError occurred at: %s - %s - %d\n", __FILE__, __FUNCTION__, __LINE__); \
|
||||
fprintf (stderr, fmt, ##__VA_ARGS__); \
|
||||
fprintf (stderr, "\n\n"); \
|
||||
LOG_DEBUG ((fmt, ##__VA_ARGS__)); \
|
||||
exit(EXIT_FAILURE); \
|
||||
} while (0)
|
||||
|
||||
#ifdef DEBUG
|
||||
#define DEBUG_TEST 1
|
||||
#else
|
||||
#define DEBUG_TEST 0
|
||||
#endif
|
||||
|
||||
/* access requests log */
|
||||
#define ACCESS_LOG(x, ...) do { access_fprintf x; } while (0)
|
||||
/* debug log */
|
||||
#define LOG_DEBUG(x, ...) do { dbg_fprintf x; } while (0)
|
||||
/* invalid requests log */
|
||||
#define LOG_INVALID(x, ...) do { invalid_fprintf x; } while (0)
|
||||
/* unknown browser log */
|
||||
#define LOG_UNKNOWNS(x, ...) do { unknowns_fprintf x; } while (0)
|
||||
/* log debug wrapper */
|
||||
#define LOG(x) do { if (DEBUG_TEST) dbg_printf x; } while (0)
|
||||
|
||||
int access_log_open (const char *path);
|
||||
void access_fprintf (const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
void access_log_close (void);
|
||||
void dbg_fprintf (const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
void dbg_log_close (void);
|
||||
void dbg_log_open (const char *file);
|
||||
void dbg_printf (const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
void invalid_fprintf (const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
void unknowns_fprintf (const char *fmt, ...) __attribute__((format (printf, 1, 2)));
|
||||
void invalid_log_close (void);
|
||||
void invalid_log_open (const char *path);
|
||||
void set_signal_data (void *p);
|
||||
void setup_sigsegv_handler (void);
|
||||
void sigsegv_handler (int sig);
|
||||
void unknowns_log_close (void);
|
||||
void unknowns_log_open (const char *path);
|
||||
|
||||
#endif
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* fileio.c -- gFile I/O abstraction layer for goaccess
|
||||
* This provides a unified interface for reading both regular and gzipped files
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "fileio.h"
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
/* Check if a file is gzipped by examining its magic bytes */
|
||||
static int
|
||||
is_gzipped_file (const char *filename) {
|
||||
FILE *fp;
|
||||
unsigned char magic[2];
|
||||
int result = 0;
|
||||
|
||||
if ((fp = fopen (filename, "rb")) == NULL)
|
||||
return 0;
|
||||
|
||||
if (fread (magic, 1, 2, fp) == 2) {
|
||||
/* gzip magic number is 0x1f 0x8b */
|
||||
if (magic[0] == 0x1f && magic[1] == 0x8b)
|
||||
result = 1;
|
||||
}
|
||||
|
||||
fclose (fp);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Open a file for reading, automatically detecting gzip compression */
|
||||
GFileHandle *
|
||||
gfile_open (const char *filename, const char *mode) {
|
||||
GFileHandle *fh;
|
||||
|
||||
if ((fh = calloc (1, sizeof (GFileHandle))) == NULL)
|
||||
return NULL;
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
/* Check if file is gzipped */
|
||||
if (is_gzipped_file (filename)) {
|
||||
fh->is_gzipped = 1;
|
||||
fh->gzfp = gzopen (filename, mode);
|
||||
if (fh->gzfp == NULL) {
|
||||
free (fh);
|
||||
return NULL;
|
||||
}
|
||||
fh->fp = NULL;
|
||||
return fh;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Open as regular file */
|
||||
#ifdef HAVE_ZLIB
|
||||
fh->is_gzipped = 0;
|
||||
fh->gzfp = NULL;
|
||||
#endif
|
||||
fh->fp = fopen (filename, mode);
|
||||
if (fh->fp == NULL) {
|
||||
free (fh);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return fh;
|
||||
}
|
||||
|
||||
/* Close a file handle */
|
||||
void
|
||||
gfile_close (GFileHandle *fh) {
|
||||
if (!fh)
|
||||
return;
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
if (fh->is_gzipped && fh->gzfp) {
|
||||
gzclose (fh->gzfp);
|
||||
} else
|
||||
#endif
|
||||
if (fh->fp) {
|
||||
fclose (fh->fp);
|
||||
}
|
||||
|
||||
free (fh);
|
||||
}
|
||||
|
||||
/* Read a line from the file */
|
||||
char *
|
||||
gfile_gets (char *buf, int size, GFileHandle *fh) {
|
||||
if (!fh || !buf || size <= 0)
|
||||
return NULL;
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
if (fh->is_gzipped && fh->gzfp) {
|
||||
return gzgets (fh->gzfp, buf, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (fh->fp)
|
||||
return fgets (buf, size, fh->fp);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Check if end of file is reached */
|
||||
int
|
||||
gfile_eof (GFileHandle *fh) {
|
||||
if (!fh)
|
||||
return 1;
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
if (fh->is_gzipped && fh->gzfp) {
|
||||
return gzeof (fh->gzfp);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (fh->fp)
|
||||
return feof (fh->fp);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Read data from file */
|
||||
int
|
||||
gfile_read (void *buf, size_t size, size_t count, GFileHandle *fh) {
|
||||
if (!fh || !buf)
|
||||
return 0;
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
if (fh->is_gzipped && fh->gzfp) {
|
||||
int bytes_to_read = size * count;
|
||||
int bytes_read = gzread (fh->gzfp, buf, bytes_to_read);
|
||||
if (bytes_read < 0)
|
||||
return 0;
|
||||
return bytes_read / size;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (fh->fp)
|
||||
return fread (buf, size, count, fh->fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Seek to a position in the file */
|
||||
int
|
||||
gfile_seek (GFileHandle *fh, long offset, int whence) {
|
||||
if (!fh)
|
||||
return -1;
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
if (fh->is_gzipped && fh->gzfp) {
|
||||
/* gzseek returns the current offset, or -1 on error */
|
||||
z_off_t result = gzseek (fh->gzfp, offset, whence);
|
||||
return (result == -1) ? -1 : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (fh->fp)
|
||||
return fseek (fh->fp, offset, whence);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get current position in file */
|
||||
long
|
||||
gfile_tell (GFileHandle *fh) {
|
||||
if (!fh)
|
||||
return -1;
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
if (fh->is_gzipped && fh->gzfp) {
|
||||
return (long) gztell (fh->gzfp);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (fh->fp)
|
||||
return ftell (fh->fp);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check for file errors */
|
||||
int
|
||||
gfile_error (GFileHandle *fh) {
|
||||
if (!fh)
|
||||
return 1;
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
if (fh->is_gzipped && fh->gzfp) {
|
||||
int errnum;
|
||||
gzerror (fh->gzfp, &errnum);
|
||||
return errnum != Z_OK && errnum != Z_STREAM_END;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (fh->fp)
|
||||
return ferror (fh->fp);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef FILEIO_H_INCLUDED
|
||||
#define FILEIO_H_INCLUDED
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
/* File handle abstraction */
|
||||
typedef struct GFileHandle_ {
|
||||
#ifdef HAVE_ZLIB
|
||||
gzFile gzfp;
|
||||
int is_gzipped;
|
||||
#endif
|
||||
FILE *fp;
|
||||
} GFileHandle;
|
||||
|
||||
/* Function prototypes */
|
||||
GFileHandle *gfile_open (const char *filename, const char *mode);
|
||||
void gfile_close (GFileHandle * fh);
|
||||
char *gfile_gets (char *buf, int size, GFileHandle * fh);
|
||||
int gfile_eof (GFileHandle * fh);
|
||||
int gfile_read (void *buf, size_t size, size_t count, GFileHandle * fh);
|
||||
int gfile_seek (GFileHandle * fh, long offset, int whence);
|
||||
long gfile_tell (GFileHandle * fh);
|
||||
int gfile_error (GFileHandle * fh);
|
||||
|
||||
#endif /* FILEIO_H_INCLUDED */
|
||||
+752
@@ -0,0 +1,752 @@
|
||||
/**
|
||||
* gchart.c -- TUI chart implementation
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <inttypes.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "gchart.h"
|
||||
#include "ui.h"
|
||||
#include "color.h"
|
||||
#include "commons.h"
|
||||
#include "gholder.h"
|
||||
#include "settings.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
#define round(x) ((x) < 0 ? (int)((x) - 0.5) : (int)((x) + 0.5))
|
||||
|
||||
/* Formatting helpers */
|
||||
|
||||
char *
|
||||
format_number_abbrev (uint64_t val, char *buf, size_t len) {
|
||||
if (val >= 1000000000ULL) {
|
||||
snprintf (buf, len, "%" PRIu64 "G", (uint64_t) ((val + 500000000ULL) / 1000000000ULL));
|
||||
} else if (val >= 1000000ULL) {
|
||||
snprintf (buf, len, "%" PRIu64 "M", (uint64_t) ((val + 500000ULL) / 1000000ULL));
|
||||
} else if (val >= 1000ULL) {
|
||||
snprintf (buf, len, "%" PRIu64 "K", (uint64_t) ((val + 500ULL) / 1000ULL));
|
||||
} else {
|
||||
snprintf (buf, len, "%" PRIu64, val);
|
||||
}
|
||||
buf[4] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *
|
||||
format_bw_abbrev (uint64_t bytes, char *buf, size_t len) {
|
||||
uint64_t v = bytes;
|
||||
const char *s = "B";
|
||||
|
||||
if (bytes >= (1ULL << 40)) {
|
||||
v = (bytes + (1ULL << 39)) >> 40;
|
||||
s = "T";
|
||||
} else if (bytes >= (1ULL << 30)) {
|
||||
v = (bytes + (1ULL << 29)) >> 30;
|
||||
s = "G";
|
||||
} else if (bytes >= (1ULL << 20)) {
|
||||
v = (bytes + (1ULL << 19)) >> 20;
|
||||
s = "M";
|
||||
} else if (bytes >= (1ULL << 10)) {
|
||||
v = (bytes + (1ULL << 9)) >> 10;
|
||||
s = "K";
|
||||
} else {
|
||||
v = bytes;
|
||||
s = "B";
|
||||
}
|
||||
|
||||
if (v >= 1000) {
|
||||
snprintf (buf, len, "%" PRIu64, bytes);
|
||||
} else {
|
||||
snprintf (buf, len, "%" PRIu64 "%s", v, s);
|
||||
}
|
||||
buf[4] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *
|
||||
format_time_abbrev (uint64_t us, char *buf, size_t len) {
|
||||
uint64_t v = us;
|
||||
const char *s = "\u00b5s";
|
||||
|
||||
if (us < 1000ULL) {
|
||||
snprintf (buf, len, "%" PRIu64 "%s", us, s);
|
||||
} else if (us < 1000000ULL) {
|
||||
v = (us + 500ULL) / 1000ULL;
|
||||
s = "ms";
|
||||
snprintf (buf, len, "%" PRIu64 "%s", v, s);
|
||||
} else if (us < 60000000ULL) {
|
||||
v = (us + 500000ULL) / 1000000ULL;
|
||||
s = "s";
|
||||
snprintf (buf, len, "%" PRIu64 "%s", v, s);
|
||||
} else if (us < 3600000000ULL) {
|
||||
v = (us + 30000000ULL) / 60000000ULL;
|
||||
s = "m";
|
||||
snprintf (buf, len, "%" PRIu64 "%s", v, s);
|
||||
} else if (us < 86400000000ULL) {
|
||||
v = (us + 1800000000ULL) / 3600000000ULL;
|
||||
s = "h";
|
||||
snprintf (buf, len, "%" PRIu64 "%s", v, s);
|
||||
} else {
|
||||
v = (us + 43200000000ULL) / 86400000000ULL;
|
||||
s = "d";
|
||||
snprintf (buf, len, "%" PRIu64 "%s", v, s);
|
||||
}
|
||||
buf[5] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char *
|
||||
format_metric_value (uint64_t val, char *buf, size_t len, int metric_type) {
|
||||
switch (metric_type) {
|
||||
case CHART_METRIC_AVGTS:
|
||||
case CHART_METRIC_CUMTS:
|
||||
case CHART_METRIC_MAXTS:
|
||||
return format_time_abbrev (val, buf, len);
|
||||
case CHART_METRIC_BW:
|
||||
return format_bw_abbrev (val, buf, len);
|
||||
default:
|
||||
return format_number_abbrev (val, buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
get_metric_name (int metric_type) {
|
||||
switch (metric_type) {
|
||||
case CHART_METRIC_HITS:
|
||||
return MTRC_HITS_LBL;
|
||||
case CHART_METRIC_VISITORS:
|
||||
return MTRC_VISITORS_LBL;
|
||||
case CHART_METRIC_BW:
|
||||
return MTRC_BW_LBL;
|
||||
case CHART_METRIC_AVGTS:
|
||||
return MTRC_AVGTS_LBL;
|
||||
case CHART_METRIC_CUMTS:
|
||||
return MTRC_CUMTS_LBL;
|
||||
case CHART_METRIC_MAXTS:
|
||||
return MTRC_MAXTS_LBL;
|
||||
default:
|
||||
return MTRC_HITS_LBL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Data helpers */
|
||||
|
||||
/* Check if a metric has data in a sub-list, recursing into nested sub-lists */
|
||||
static int
|
||||
sub_list_has_metric (GSubList *sl, int metric_type) {
|
||||
GSubItem *sub;
|
||||
GMetrics *m;
|
||||
|
||||
if (sl == NULL)
|
||||
return 0;
|
||||
|
||||
for (sub = sl->head; sub; sub = sub->next) {
|
||||
m = sub->metrics;
|
||||
if (m) {
|
||||
switch (metric_type) {
|
||||
case CHART_METRIC_HITS:
|
||||
if (m->hits > 0)
|
||||
return 1;
|
||||
break;
|
||||
case CHART_METRIC_VISITORS:
|
||||
if (m->visitors > 0)
|
||||
return 1;
|
||||
break;
|
||||
case CHART_METRIC_BW:
|
||||
if (m->nbw > 0)
|
||||
return 1;
|
||||
break;
|
||||
case CHART_METRIC_AVGTS:
|
||||
if (conf.serve_usecs && m->avgts.nts > 0)
|
||||
return 1;
|
||||
break;
|
||||
case CHART_METRIC_CUMTS:
|
||||
if (conf.serve_usecs && m->cumts.nts > 0)
|
||||
return 1;
|
||||
break;
|
||||
case CHART_METRIC_MAXTS:
|
||||
if (conf.serve_usecs && m->maxts.nts > 0)
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
if (m->hits > 0)
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sub->sub_list != NULL && sub_list_has_metric (sub->sub_list, metric_type))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
metric_has_data (GHolder *h, int metric_type) {
|
||||
uint32_t i;
|
||||
int has = 0;
|
||||
GMetrics *m;
|
||||
|
||||
if (!h || h->idx == 0)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < h->idx; i++) {
|
||||
m = h->items[i].metrics;
|
||||
if (m) {
|
||||
switch (metric_type) {
|
||||
case CHART_METRIC_HITS:
|
||||
if (m->hits > 0)
|
||||
has = 1;
|
||||
break;
|
||||
case CHART_METRIC_VISITORS:
|
||||
if (m->visitors > 0)
|
||||
has = 1;
|
||||
break;
|
||||
case CHART_METRIC_BW:
|
||||
if (m->nbw > 0)
|
||||
has = 1;
|
||||
break;
|
||||
case CHART_METRIC_AVGTS:
|
||||
if (conf.serve_usecs && m->avgts.nts > 0)
|
||||
has = 1;
|
||||
break;
|
||||
case CHART_METRIC_CUMTS:
|
||||
if (conf.serve_usecs && m->cumts.nts > 0)
|
||||
has = 1;
|
||||
break;
|
||||
case CHART_METRIC_MAXTS:
|
||||
if (conf.serve_usecs && m->maxts.nts > 0)
|
||||
has = 1;
|
||||
break;
|
||||
default:
|
||||
if (m->hits > 0)
|
||||
has = 1;
|
||||
break;
|
||||
}
|
||||
if (has)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (h->module != HOSTS && h->items[i].sub_list) {
|
||||
if (sub_list_has_metric (h->items[i].sub_list, metric_type))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
get_available_metrics (GModule module, int metrics[CHART_METRIC_COUNT]) {
|
||||
int count = 0;
|
||||
const GOutput *out = output_lookup (module);
|
||||
if (!out)
|
||||
return 0;
|
||||
|
||||
if (out->hits)
|
||||
metrics[count++] = CHART_METRIC_HITS;
|
||||
if (out->visitors)
|
||||
metrics[count++] = CHART_METRIC_VISITORS;
|
||||
if (out->bw && conf.bandwidth)
|
||||
metrics[count++] = CHART_METRIC_BW;
|
||||
if (out->avgts && conf.serve_usecs)
|
||||
metrics[count++] = CHART_METRIC_AVGTS;
|
||||
if (out->cumts && conf.serve_usecs)
|
||||
metrics[count++] = CHART_METRIC_CUMTS;
|
||||
if (out->maxts && conf.serve_usecs)
|
||||
metrics[count++] = CHART_METRIC_MAXTS;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
get_metric_value (GMetrics *metrics, int metric_type) {
|
||||
if (!metrics)
|
||||
return 0;
|
||||
switch (metric_type) {
|
||||
case CHART_METRIC_HITS:
|
||||
return metrics->hits;
|
||||
case CHART_METRIC_VISITORS:
|
||||
return metrics->visitors;
|
||||
case CHART_METRIC_BW:
|
||||
return metrics->nbw;
|
||||
case CHART_METRIC_AVGTS:
|
||||
return conf.serve_usecs ? metrics->avgts.nts : 0;
|
||||
case CHART_METRIC_CUMTS:
|
||||
return conf.serve_usecs ? metrics->cumts.nts : 0;
|
||||
case CHART_METRIC_MAXTS:
|
||||
return conf.serve_usecs ? metrics->maxts.nts : 0;
|
||||
default:
|
||||
return metrics->hits;
|
||||
}
|
||||
}
|
||||
|
||||
/* Chart item flattening */
|
||||
|
||||
typedef struct {
|
||||
GMetrics *metrics;
|
||||
int is_subitem;
|
||||
int flat_idx;
|
||||
} ChartItem;
|
||||
|
||||
/* Recursively count all sub-items in a sub-list tree (chart-local). */
|
||||
static int
|
||||
count_sub_items_recursive_chart (GSubList *sl) {
|
||||
GSubItem *iter;
|
||||
int count = 0;
|
||||
if (sl == NULL)
|
||||
return 0;
|
||||
for (iter = sl->head; iter; iter = iter->next) {
|
||||
count++;
|
||||
if (iter->sub_list)
|
||||
count += count_sub_items_recursive_chart (iter->sub_list);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Count visible sub-items recursively given per-node expand state (chart-local). */
|
||||
static int
|
||||
count_visible_sub_chart (GSubList *sl, const uint8_t *node_exp, int node_exp_size, int *full_idx) {
|
||||
GSubItem *iter;
|
||||
int count = 0;
|
||||
if (sl == NULL)
|
||||
return 0;
|
||||
for (iter = sl->head; iter; iter = iter->next) {
|
||||
int my_full = *full_idx;
|
||||
count++;
|
||||
(*full_idx)++;
|
||||
if (iter->sub_list && iter->sub_list->size > 0) {
|
||||
int expanded = 1;
|
||||
if (node_exp != NULL && my_full < node_exp_size)
|
||||
expanded = node_exp[my_full];
|
||||
if (expanded)
|
||||
count += count_visible_sub_chart (iter->sub_list, node_exp, node_exp_size, full_idx);
|
||||
else
|
||||
*full_idx += count_sub_items_recursive_chart (iter->sub_list);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Recursively flatten visible sub-items into the chart items array */
|
||||
static void
|
||||
flatten_sub_items (GSubList *sl, ChartItem *items, int *idx, int depth, int *full_idx,
|
||||
const uint8_t *node_exp, int node_exp_size) {
|
||||
GSubItem *sub;
|
||||
if (sl == NULL)
|
||||
return;
|
||||
for (sub = sl->head; sub; sub = sub->next) {
|
||||
int my_full = *full_idx;
|
||||
items[*idx] = (ChartItem) {
|
||||
.metrics = sub->metrics,.is_subitem = depth,.flat_idx = *idx};
|
||||
(*idx)++;
|
||||
(*full_idx)++;
|
||||
if (sub->sub_list != NULL && sub->sub_list->size > 0) {
|
||||
int expanded = 1;
|
||||
if (node_exp != NULL && my_full < node_exp_size)
|
||||
expanded = node_exp[my_full];
|
||||
if (expanded)
|
||||
flatten_sub_items (sub->sub_list, items, idx, depth + 1, full_idx, node_exp, node_exp_size);
|
||||
else
|
||||
*full_idx += count_sub_items_recursive_chart (sub->sub_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
build_chart_items (GHolder *h, ChartItem **out, const uint8_t *item_expanded,
|
||||
int item_expanded_size) {
|
||||
uint32_t i;
|
||||
int count = h->idx, idx = 0, full_idx = 0;
|
||||
ChartItem *items;
|
||||
|
||||
/* Only include sub-items if we have expand state (i.e., panel is expanded) */
|
||||
if (h->module != HOSTS && item_expanded != NULL && item_expanded_size > 0) {
|
||||
/* Count visible items using per-node expand state */
|
||||
int fi = 0;
|
||||
for (i = 0; i < h->idx; i++) {
|
||||
int my_full = fi;
|
||||
fi++;
|
||||
if (h->items[i].sub_list && h->items[i].sub_list->size > 0) {
|
||||
int expanded = 1;
|
||||
if (my_full < item_expanded_size)
|
||||
expanded = item_expanded[my_full];
|
||||
if (expanded)
|
||||
count +=
|
||||
count_visible_sub_chart (h->items[i].sub_list, item_expanded, item_expanded_size, &fi);
|
||||
else
|
||||
fi += count_sub_items_recursive_chart (h->items[i].sub_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* If item_expanded is NULL, we only render root items (count = h->idx already) */
|
||||
|
||||
items = xcalloc (count, sizeof (ChartItem));
|
||||
|
||||
for (i = 0; i < h->idx; i++) {
|
||||
int my_full = full_idx;
|
||||
items[idx] = (ChartItem) {
|
||||
.metrics = h->items[i].metrics,.is_subitem = 0,.flat_idx = idx};
|
||||
idx++;
|
||||
full_idx++;
|
||||
|
||||
/* Only flatten sub-items if panel is expanded (item_expanded != NULL) */
|
||||
if (h->module != HOSTS && item_expanded != NULL && h->items[i].sub_list &&
|
||||
h->items[i].sub_list->size > 0) {
|
||||
int should_expand = 1;
|
||||
if (my_full < item_expanded_size)
|
||||
should_expand = item_expanded[my_full];
|
||||
if (should_expand)
|
||||
flatten_sub_items (h->items[i].sub_list, items, &idx, 1, &full_idx, item_expanded,
|
||||
item_expanded_size);
|
||||
else
|
||||
full_idx += count_sub_items_recursive_chart (h->items[i].sub_list);
|
||||
}
|
||||
}
|
||||
|
||||
*out = items;
|
||||
return idx;
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
apply_log_scale (uint64_t value, uint64_t max_value) {
|
||||
double log_val, log_max;
|
||||
if (value == 0 || max_value == 0)
|
||||
return 0;
|
||||
log_val = log10 ((double) value + 1.0);
|
||||
log_max = log10 ((double) max_value + 1.0);
|
||||
if (log_max == 0)
|
||||
return value;
|
||||
return (uint64_t) ((log_val / log_max) * max_value);
|
||||
}
|
||||
|
||||
/* Drawing primitives */
|
||||
static void
|
||||
draw_vbar (ChartDrawCtx *ctx, int x, int filled, GColors *color, int is_selected,
|
||||
uint64_t actual_val) {
|
||||
WINDOW *w = ctx->win;
|
||||
int y_top = ctx->y_chart_start;
|
||||
int width = ctx->bar_width;
|
||||
int i, y, ly, lx;
|
||||
char buf[16];
|
||||
GColors *val_col;
|
||||
|
||||
for (i = 0; i < ctx->bar_height; i++)
|
||||
mvwhline (w, y_top + i, x, ' ', width);
|
||||
|
||||
if (is_selected) {
|
||||
/* Use COLOR_SELECTED colors + underline + bold for selected bar */
|
||||
GColors *sel = get_color (COLOR_SELECTED);
|
||||
wattron (w, COLOR_PAIR (sel->pair->idx) | A_BOLD | A_UNDERLINE);
|
||||
} else {
|
||||
/* Normal bar: original color + reverse */
|
||||
wattron (w, COLOR_PAIR (color->pair->idx) | color->attr | A_REVERSE);
|
||||
}
|
||||
|
||||
/* Draw filled part */
|
||||
for (i = 0; i < filled; i++) {
|
||||
y = y_top + ctx->bar_height - 1 - i;
|
||||
mvwhline (w, y, x, ' ', width);
|
||||
}
|
||||
|
||||
/* Turn off what we turned on */
|
||||
wattroff (w, COLOR_PAIR (color->pair->idx) | color->attr | A_REVERSE | A_BOLD | A_UNDERLINE);
|
||||
|
||||
/* Value label (unchanged) */
|
||||
if (actual_val > 0 && ctx->show_bar_values) {
|
||||
format_metric_value (actual_val, buf, sizeof (buf), ctx->metric_type);
|
||||
y = y_top + ctx->bar_height - filled;
|
||||
ly = y - 1;
|
||||
if (ly < y_top - 1)
|
||||
ly = y_top - 1;
|
||||
|
||||
val_col = get_color (COLOR_CHART_VALUES);
|
||||
wattron (w, COLOR_PAIR (val_col->pair->idx) | val_col->attr);
|
||||
if (is_selected)
|
||||
wattron (w, A_BOLD);
|
||||
lx = x + (width - (int) strlen (buf)) / 2;
|
||||
if (lx < x)
|
||||
lx = x;
|
||||
mvwprintw (w, ly, lx, "%s", buf);
|
||||
wattroff (w, COLOR_PAIR (val_col->pair->idx) | val_col->attr);
|
||||
if (is_selected)
|
||||
wattroff (w, A_BOLD);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
draw_axes (ChartDrawCtx *ctx) {
|
||||
WINDOW *w;
|
||||
int ys, xs, h, i, yt, lx;
|
||||
GColors *axis;
|
||||
uint64_t maxv;
|
||||
char buf[16];
|
||||
const int ticks = 4;
|
||||
|
||||
if (!ctx->show_axes)
|
||||
return;
|
||||
|
||||
w = ctx->win;
|
||||
ys = ctx->y_chart_start;
|
||||
xs = ctx->x_start;
|
||||
h = ctx->bar_height;
|
||||
axis = get_color (COLOR_CHART_AXIS);
|
||||
maxv = ctx->display_max;
|
||||
|
||||
wattron (w, axis->attr | COLOR_PAIR (axis->pair->idx));
|
||||
|
||||
for (i = 0; i <= h; i++)
|
||||
mvwaddch (w, ys + i, xs - 2, '|');
|
||||
|
||||
for (i = 0; i < getmaxx (w) - xs - 2; i++)
|
||||
mvwaddch (w, ys + h, xs + i, '-');
|
||||
|
||||
for (i = 0; i <= ticks; i++) {
|
||||
uint64_t v = (maxv * (uint64_t) i + ticks / 2) / ticks;
|
||||
yt = ys + h - ((v * (int64_t) h + maxv / 2) / maxv);
|
||||
format_metric_value (v, buf, sizeof (buf), ctx->metric_type);
|
||||
lx = xs - 3 - (int) strlen (buf);
|
||||
if (lx < 0)
|
||||
lx = 0;
|
||||
mvwprintw (w, yt, lx, "%s", buf);
|
||||
mvwaddch (w, yt, xs - 2, '+');
|
||||
}
|
||||
|
||||
wattroff (w, axis->attr | COLOR_PAIR (axis->pair->idx));
|
||||
}
|
||||
|
||||
static void
|
||||
draw_chart_indicator (ChartDrawCtx *ctx) {
|
||||
char ind[96], mname[64];
|
||||
GColors *col = get_color (COLOR_CHART_AXIS);
|
||||
|
||||
strncpy (mname, get_metric_name (ctx->metric_type), sizeof (mname) - 1);
|
||||
mname[sizeof (mname) - 1] = '\0';
|
||||
for (char *p = mname; *p; p++)
|
||||
*p = toupper ((unsigned char) *p);
|
||||
|
||||
snprintf (ind, sizeof (ind), "[%s:%s%s]",
|
||||
mname, ctx->use_log_scale ? "LOG" : "LINEAR", ctx->reverse_bars ? ":REV" : "");
|
||||
|
||||
wattron (ctx->win, col->attr | COLOR_PAIR (col->pair->idx));
|
||||
mvwprintw (ctx->win, ctx->y_start, 1, "%s", ind);
|
||||
wattroff (ctx->win, col->attr | COLOR_PAIR (col->pair->idx));
|
||||
}
|
||||
|
||||
/* Window & selection helpers */
|
||||
|
||||
static int
|
||||
get_chart_selected_root (const ChartDrawCtx *ctx) {
|
||||
int cum = 0, subsz = 0;
|
||||
uint32_t j;
|
||||
if (ctx->selected_idx < 0)
|
||||
return -1;
|
||||
if (ctx->holder->module != HOSTS)
|
||||
return ctx->selected_idx;
|
||||
|
||||
for (j = 0; j < ctx->holder->idx; j++) {
|
||||
subsz = ctx->holder->items[j].sub_list ? ctx->holder->items[j].sub_list->size : 0;
|
||||
if (ctx->selected_idx == cum)
|
||||
return j;
|
||||
cum += 1 + subsz;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
compute_bar_window (ChartDrawCtx *ctx, int num_items) {
|
||||
int start = ctx->scroll_offset;
|
||||
int vis = ctx->visible_bars;
|
||||
int sel = get_chart_selected_root (ctx);
|
||||
|
||||
if (sel >= 0 && num_items > vis) {
|
||||
if (sel < start)
|
||||
start = sel;
|
||||
else if (sel >= start + vis)
|
||||
start = sel - vis + 1;
|
||||
}
|
||||
|
||||
if (start < 0)
|
||||
start = 0;
|
||||
if (num_items > vis && start > num_items - vis)
|
||||
start = num_items - vis;
|
||||
if (num_items <= vis)
|
||||
start = 0;
|
||||
|
||||
ctx->start_bar = start;
|
||||
ctx->end_bar = start + vis;
|
||||
if (ctx->end_bar > num_items)
|
||||
ctx->end_bar = num_items;
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
compute_local_max (ChartItem *items, const ChartDrawCtx *ctx) {
|
||||
uint64_t mx = 0;
|
||||
for (int i = ctx->start_bar; i < ctx->end_bar; i++) {
|
||||
uint64_t v = get_metric_value (items[i].metrics, ctx->metric_type);
|
||||
if (v > mx)
|
||||
mx = v;
|
||||
}
|
||||
return mx;
|
||||
}
|
||||
|
||||
static void
|
||||
compute_chart_window (ChartDrawCtx *ctx, ChartItem *items) {
|
||||
compute_bar_window (ctx, ctx->num_items);
|
||||
ctx->display_max = compute_local_max (items, ctx);
|
||||
}
|
||||
|
||||
static void
|
||||
draw_chart_bars (ChartDrawCtx *ctx, ChartItem *items) {
|
||||
GColors *bar_c = get_color (COLOR_BARS), *c = NULL;
|
||||
GColors *sub_c = get_color (COLOR_SUBBARS);
|
||||
int sel_root = get_chart_selected_root (ctx);
|
||||
int i, x, data_idx, vis_pos, is_sel;
|
||||
uint64_t val, dval;
|
||||
int filled;
|
||||
|
||||
for (i = ctx->start_bar; i < ctx->end_bar; i++) {
|
||||
data_idx = ctx->reverse_bars ? (ctx->end_bar - 1 - (i - ctx->start_bar)) : i;
|
||||
if (data_idx >= ctx->num_items)
|
||||
continue;
|
||||
|
||||
val = get_metric_value (items[data_idx].metrics, ctx->metric_type);
|
||||
dval = ctx->use_log_scale ? apply_log_scale (val, ctx->display_max) : val;
|
||||
|
||||
filled = (int) ((dval * (int64_t) ctx->bar_height + ctx->display_max / 2) / ctx->display_max);
|
||||
if (filled > ctx->bar_height)
|
||||
filled = ctx->bar_height;
|
||||
|
||||
vis_pos = i - ctx->start_bar;
|
||||
x = ctx->x_start + vis_pos * (ctx->bar_width + ctx->bar_gap);
|
||||
|
||||
is_sel = (items[data_idx].flat_idx == sel_root);
|
||||
c = items[data_idx].is_subitem ? sub_c : bar_c;
|
||||
|
||||
draw_vbar (ctx, x, filled, c, is_sel, ctx->show_bar_values ? val : 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
draw_chart_caret (ChartDrawCtx *ctx, ChartItem *items) {
|
||||
int sel_root = get_chart_selected_root (ctx);
|
||||
if (sel_root < 0)
|
||||
return;
|
||||
|
||||
for (int i = ctx->start_bar; i < ctx->end_bar; i++) {
|
||||
int didx = ctx->reverse_bars ? (ctx->end_bar - 1 - (i - ctx->start_bar)) : i;
|
||||
if (didx >= ctx->num_items)
|
||||
continue;
|
||||
|
||||
if (items[didx].flat_idx == sel_root) {
|
||||
int pos = i - ctx->start_bar;
|
||||
int x = ctx->x_start + pos * (ctx->bar_width + ctx->bar_gap) + ctx->bar_width / 2;
|
||||
mvwaddch (ctx->win, ctx->y_start + ctx->chart_height, x, '^');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
check_top_padding (ChartDrawCtx *ctx, ChartItem *items) {
|
||||
ctx->needs_top_padding = 0;
|
||||
for (int i = ctx->start_bar; i < ctx->end_bar; i++) {
|
||||
uint64_t v = get_metric_value (items[i].metrics, ctx->metric_type);
|
||||
uint64_t dv = ctx->use_log_scale ? apply_log_scale (v, ctx->display_max) : v;
|
||||
int f = (int) ((dv * (int64_t) ctx->bar_height + ctx->display_max / 2) / ctx->display_max);
|
||||
if (f >= ctx->bar_height) {
|
||||
ctx->needs_top_padding = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Main function */
|
||||
|
||||
void
|
||||
draw_panel_chart (WINDOW *win, GHolder *h, const ChartDrawCtx *user_opts) {
|
||||
uint64_t global_max = 0, v = 0;
|
||||
int i = 0, y = 0, height = 0, screen_w = 0;
|
||||
ChartDrawCtx ctx = *user_opts;
|
||||
ChartItem *items = NULL;
|
||||
ctx.win = win;
|
||||
ctx.holder = h;
|
||||
ctx.x_start = ctx.show_axes ? 8 : 2;
|
||||
|
||||
ctx.num_items = build_chart_items (h, &items, ctx.item_expanded, ctx.item_expanded_size);
|
||||
if (ctx.num_items == 0) {
|
||||
free (items);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Early exit if no data for this metric */
|
||||
for (i = 0; i < ctx.num_items; i++) {
|
||||
v = get_metric_value (items[i].metrics, ctx.metric_type);
|
||||
if (v > global_max)
|
||||
global_max = v;
|
||||
}
|
||||
if (global_max == 0) {
|
||||
free (items);
|
||||
return;
|
||||
}
|
||||
|
||||
draw_chart_indicator (&ctx);
|
||||
|
||||
y = ctx.y_start + 1;
|
||||
height = ctx.chart_height - 1;
|
||||
ctx.bar_height = height - 1;
|
||||
|
||||
screen_w = getmaxx (win) - ctx.x_start - 2;
|
||||
if (ctx.auto_width) {
|
||||
ctx.bar_gap = 1;
|
||||
ctx.bar_width = (screen_w - (ctx.num_items - 1) * ctx.bar_gap) / ctx.num_items;
|
||||
if (ctx.bar_width < 1)
|
||||
ctx.bar_width = 1;
|
||||
} else {
|
||||
ctx.bar_width = BAR_W;
|
||||
ctx.bar_gap = BAR_GAP;
|
||||
}
|
||||
|
||||
ctx.visible_bars = screen_w / (ctx.bar_width + ctx.bar_gap);
|
||||
if (ctx.visible_bars < 1)
|
||||
ctx.visible_bars = 1;
|
||||
|
||||
compute_chart_window (&ctx, items);
|
||||
check_top_padding (&ctx, items);
|
||||
ctx.y_chart_start = y + (ctx.needs_top_padding ? 1 : 0);
|
||||
|
||||
draw_chart_bars (&ctx, items);
|
||||
draw_axes (&ctx);
|
||||
draw_chart_caret (&ctx, items);
|
||||
|
||||
free (items);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GCHART_H_INCLUDED
|
||||
#define GCHART_H_INCLUDED
|
||||
|
||||
#include <ncurses.h>
|
||||
#include "commons.h"
|
||||
|
||||
/* Forward declarations */
|
||||
typedef struct GHolder_ GHolder;
|
||||
|
||||
#define DASH_CHART_HEIGHT 7
|
||||
#define BAR_W 5
|
||||
#define BAR_GAP 1
|
||||
|
||||
typedef enum {
|
||||
CHART_METRIC_HITS = 0,
|
||||
CHART_METRIC_VISITORS,
|
||||
CHART_METRIC_BW,
|
||||
CHART_METRIC_AVGTS,
|
||||
CHART_METRIC_CUMTS,
|
||||
CHART_METRIC_MAXTS,
|
||||
CHART_METRIC_COUNT
|
||||
} GChartMetric;
|
||||
|
||||
/* All drawing options + computed state in one place */
|
||||
typedef struct {
|
||||
WINDOW *win;
|
||||
GHolder *holder;
|
||||
int y_start;
|
||||
int chart_height;
|
||||
int auto_width;
|
||||
int selected_idx;
|
||||
int scroll_offset;
|
||||
int show_axes;
|
||||
int show_bar_values;
|
||||
int metric_type;
|
||||
int use_log_scale;
|
||||
int reverse_bars;
|
||||
|
||||
/* Computed values */
|
||||
int x_start;
|
||||
int bar_width;
|
||||
int bar_gap;
|
||||
int visible_bars;
|
||||
int start_bar;
|
||||
int end_bar;
|
||||
uint64_t display_max;
|
||||
int bar_height;
|
||||
int y_chart_start;
|
||||
int needs_top_padding;
|
||||
int num_items; /* total number of chart items */
|
||||
|
||||
/* Per-item expand state for filtering visible chart bars */
|
||||
const uint8_t *item_expanded; /* NULL = all expanded, else per-root-item state */
|
||||
int item_expanded_size; /* number of entries in item_expanded */
|
||||
} ChartDrawCtx;
|
||||
|
||||
/* Main drawing function */
|
||||
void draw_panel_chart (WINDOW * win, GHolder * h, const ChartDrawCtx * opts);
|
||||
|
||||
int metric_has_data (GHolder * h, int metric_type);
|
||||
int get_available_metrics (GModule module, int metrics[CHART_METRIC_COUNT]);
|
||||
char *format_number_abbrev (uint64_t val, char *buf, size_t len);
|
||||
|
||||
#endif
|
||||
+1781
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifndef GDASHBOARD_H_INCLUDED
|
||||
#define GDASHBOARD_H_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "ui.h"
|
||||
|
||||
/* IMPORTANT: These defines are BASE VALUES used for calculation.
|
||||
* Actual positions are calculated dynamically in render_content() */
|
||||
|
||||
#define DASH_HEAD_POS 0 /* position of header line */
|
||||
#define DASH_EMPTY_POS 1 /* empty line position */
|
||||
|
||||
/* Chart positioning - these are fixed */
|
||||
#define DASH_CHART_START 2 /* first chart row */
|
||||
#define DASH_CHART_END (DASH_CHART_START + DASH_CHART_HEIGHT - 1) /* last chart row */
|
||||
|
||||
/* These are reference values - actual positions calculated dynamically */
|
||||
#define DASH_BLANK1_POS (DASH_CHART_END + 1) /* blank after chart (for caret) */
|
||||
#define DASH_BLANK2_POS (DASH_CHART_END + 2) /* blank before cols (if cols enabled) */
|
||||
#define DASH_COLS_POS (DASH_CHART_END + 3) /* position of column names (if enabled) */
|
||||
#define DASH_DASHES_POS (DASH_CHART_END + 4) /* position of dashes (if enabled) */
|
||||
#define DASH_DATA_POS (DASH_CHART_END + 5) /* data line position (with cols) */
|
||||
|
||||
/* Helper macros for calculations */
|
||||
#define DASH_NON_DATA (DASH_DATA_POS) /* number of rows without data stats */
|
||||
#define DASH_COL_ROWS 2 /* number of rows for column values + dashed lines */
|
||||
|
||||
/* Panel sizes - these determine how many rows each panel occupies
|
||||
* Structure:
|
||||
* With columns: header + blank + chart(7) + blank + blank + cols + dashes + data(N) + blank = 13 + N
|
||||
* Without columns: header + blank + chart(7) + blank + data(N) + blank = 11 + N
|
||||
*/
|
||||
#define DASH_COLLAPSED (DASH_DATA_POS + 8 + 1) /* +1 for trailing blank */
|
||||
#define DASH_EXPANDED (DASH_DATA_POS + 28 + 1) /* +1 for trailing blank */
|
||||
|
||||
/* Other constants */
|
||||
#define DASH_INIT_X 1 /* start position (x-axis) */
|
||||
#define DASH_BW_LEN 11 /* max bandwidth string length */
|
||||
#define DASH_SRV_TM_LEN 9 /* max time served length */
|
||||
#define DASH_SPACE 1 /* space between columns (metrics) */
|
||||
|
||||
typedef struct {
|
||||
int header_pos;
|
||||
int blank_after_header;
|
||||
int chart_start;
|
||||
int chart_end;
|
||||
int blank_for_caret;
|
||||
int blank_before_cols;
|
||||
int cols_header_pos;
|
||||
int cols_dashes_pos;
|
||||
int data_start;
|
||||
int data_end;
|
||||
} PanelLayout;
|
||||
|
||||
/* Common render data line fields */
|
||||
typedef struct GDashRender_ {
|
||||
WINDOW *win;
|
||||
int y;
|
||||
int w;
|
||||
int idx;
|
||||
int sel;
|
||||
} GDashRender;
|
||||
|
||||
/* Dashboard panel item */
|
||||
typedef struct GDashData_ {
|
||||
GMetrics *metrics;
|
||||
short is_subitem;
|
||||
short has_children; /* 1 if this node has sub-items */
|
||||
int node_full_idx; /* index into node_expanded[] (full DFS position) */
|
||||
} GDashData;
|
||||
|
||||
/* Dashboard panel meta data */
|
||||
typedef struct GDashMeta_ {
|
||||
uint64_t max_hits; /* maximum value on the hits column */
|
||||
uint64_t max_visitors; /* maximum value on the visitors column */
|
||||
uint64_t max_bw;
|
||||
|
||||
/* determine the maximum metric's length of these metrics */
|
||||
/* for instance, 1022 is the max value for the hits column and its length = 4 */
|
||||
int hits_len;
|
||||
int hits_perc_len;
|
||||
int visitors_len;
|
||||
int visitors_perc_len;
|
||||
int bw_len;
|
||||
int bw_perc_len;
|
||||
int avgts_len;
|
||||
int cumts_len;
|
||||
int maxts_len;
|
||||
int method_len;
|
||||
int protocol_len;
|
||||
int data_len;
|
||||
} GDashMeta;
|
||||
|
||||
/* Dashboard panel */
|
||||
typedef struct GDashModule_ {
|
||||
GDashData *data; /* data metrics */
|
||||
GModule module; /* module */
|
||||
GDashMeta meta; /* meta data */
|
||||
|
||||
const char *head; /* panel header */
|
||||
const char *desc; /* panel description */
|
||||
|
||||
uint32_t alloc_data; /* number of data items allocated. */
|
||||
/* e.g., MAX_CHOICES or holder size */
|
||||
int dash_size; /* dashboard size */
|
||||
uint32_t holder_size; /* hash table size */
|
||||
uint32_t ht_size; /* hash table size */
|
||||
int idx_data; /* idx data */
|
||||
|
||||
unsigned short pos_y; /* dashboard current Y position */
|
||||
} GDashModule;
|
||||
|
||||
/* Dashboard */
|
||||
typedef struct GDash_ {
|
||||
int total_alloc; /* number of allocated dashboard lines */
|
||||
GDashModule module[TOTAL_MODULES];
|
||||
} GDash;
|
||||
|
||||
/* Function Prototypes */
|
||||
GDashData *new_gdata (uint32_t size);
|
||||
GDash *new_gdash (void);
|
||||
uint32_t get_num_collapsed_data_rows (void);
|
||||
uint32_t get_num_expanded_data_rows (void);
|
||||
int perform_next_find (GHolder * h, GScroll * scroll);
|
||||
int render_find_dialog (WINDOW * main_win, GScroll * scroll);
|
||||
int set_module_from_mouse_event (GScroll * scroll, GDash * dash, int y);
|
||||
uint32_t get_ht_size_by_module (GModule module);
|
||||
void display_content (WINDOW * win, GDash * dash, GScroll * gscroll, GHolder * holder);
|
||||
void free_dashboard (GDash * dash);
|
||||
void load_data_to_dash (GHolder * h, GDash * dash, GModule module, GScroll * scroll);
|
||||
void reset_find (void);
|
||||
void reset_scroll_offsets (GScroll * scroll);
|
||||
|
||||
#endif
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* gdns.c -- hosts resolver
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#define _MULTI_THREADED
|
||||
#ifdef __FreeBSD__
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <pthread.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "gdns.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "gkhash.h"
|
||||
#include "goaccess.h"
|
||||
#include "util.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
GDnsThread gdns_thread;
|
||||
static GDnsQueue *gdns_queue;
|
||||
|
||||
/* Initialize the queue. */
|
||||
void
|
||||
gqueue_init (GDnsQueue *q, int capacity) {
|
||||
q->head = 0;
|
||||
q->tail = -1;
|
||||
q->size = 0;
|
||||
q->capacity = capacity;
|
||||
}
|
||||
|
||||
/* Get the current size of queue.
|
||||
*
|
||||
* Returns the size of the queue. */
|
||||
int
|
||||
gqueue_size (GDnsQueue *q) {
|
||||
return q->size;
|
||||
}
|
||||
|
||||
/* Determine if the queue is empty.
|
||||
*
|
||||
* Returns true if empty, otherwise false. */
|
||||
int
|
||||
gqueue_empty (GDnsQueue *q) {
|
||||
return q->size == 0;
|
||||
}
|
||||
|
||||
/* Determine if the queue is full.
|
||||
*
|
||||
* Returns true if full, otherwise false. */
|
||||
int
|
||||
gqueue_full (GDnsQueue *q) {
|
||||
return q->size == q->capacity;
|
||||
}
|
||||
|
||||
/* Free the queue. */
|
||||
void
|
||||
gqueue_destroy (GDnsQueue *q) {
|
||||
free (q);
|
||||
}
|
||||
|
||||
/* Add at the end of the queue a string item.
|
||||
*
|
||||
* If the queue is full, -1 is returned.
|
||||
* If added to the queue, 0 is returned. */
|
||||
int
|
||||
gqueue_enqueue (GDnsQueue *q, const char *item) {
|
||||
if (gqueue_full (q))
|
||||
return -1;
|
||||
|
||||
q->tail = (q->tail + 1) % q->capacity;
|
||||
strncpy (q->buffer[q->tail], item, sizeof (q->buffer[q->tail]));
|
||||
q->buffer[q->tail][sizeof (q->buffer[q->tail]) - 1] = '\0';
|
||||
q->size++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Find a string item in the queue.
|
||||
*
|
||||
* If the queue is empty, or the item is not in the queue, 0 is returned.
|
||||
* If found, 1 is returned. */
|
||||
int
|
||||
gqueue_find (GDnsQueue *q, const char *item) {
|
||||
int i;
|
||||
if (gqueue_empty (q))
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < q->size; i++) {
|
||||
if (strcmp (item, q->buffer[i]) == 0)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remove a string item from the head of the queue.
|
||||
*
|
||||
* If the queue is empty, NULL is returned.
|
||||
* If removed, the string item is returned. */
|
||||
char *
|
||||
gqueue_dequeue (GDnsQueue *q) {
|
||||
char *item;
|
||||
if (gqueue_empty (q))
|
||||
return NULL;
|
||||
|
||||
item = q->buffer[q->head];
|
||||
q->head = (q->head + 1) % q->capacity;
|
||||
q->size--;
|
||||
return item;
|
||||
}
|
||||
|
||||
/* Get the corresponding hostname given an IP address.
|
||||
*
|
||||
* On error, a string error message is returned.
|
||||
* On success, a malloc'd hostname is returned. */
|
||||
static char *
|
||||
reverse_host (const struct sockaddr *a, socklen_t length) {
|
||||
char h[H_SIZE] = { 0 };
|
||||
int flags, st;
|
||||
|
||||
flags = NI_NAMEREQD;
|
||||
st = getnameinfo (a, length, h, H_SIZE, NULL, 0, flags);
|
||||
if (!st) {
|
||||
/* BSD returns \0 while Linux . on solve lookups */
|
||||
if (*h == '\0')
|
||||
return alloc_string (".");
|
||||
return alloc_string (h);
|
||||
}
|
||||
return alloc_string (gai_strerror (st));
|
||||
}
|
||||
|
||||
/* Determine if IPv4 or IPv6 and resolve.
|
||||
*
|
||||
* On error, NULL is returned.
|
||||
* On success, a malloc'd hostname is returned. */
|
||||
char *
|
||||
reverse_ip (char *str) {
|
||||
union {
|
||||
struct sockaddr addr;
|
||||
struct sockaddr_in6 addr6;
|
||||
struct sockaddr_in addr4;
|
||||
} a;
|
||||
|
||||
if (str == NULL || *str == '\0')
|
||||
return NULL;
|
||||
|
||||
memset (&a, 0, sizeof (a));
|
||||
if (1 == inet_pton (AF_INET, str, &a.addr4.sin_addr)) {
|
||||
a.addr4.sin_family = AF_INET;
|
||||
return reverse_host (&a.addr, sizeof (a.addr4));
|
||||
} else if (1 == inet_pton (AF_INET6, str, &a.addr6.sin6_addr)) {
|
||||
a.addr6.sin6_family = AF_INET6;
|
||||
return reverse_host (&a.addr, sizeof (a.addr6));
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Producer - Resolve an IP address and add it to the queue. */
|
||||
void
|
||||
dns_resolver (char *addr) {
|
||||
pthread_mutex_lock (&gdns_thread.mutex);
|
||||
/* queue is not full and the IP address is not in the queue */
|
||||
if (!gqueue_full (gdns_queue) && !gqueue_find (gdns_queue, addr)) {
|
||||
/* add the IP to the queue */
|
||||
gqueue_enqueue (gdns_queue, addr);
|
||||
pthread_cond_broadcast (&gdns_thread.not_empty);
|
||||
}
|
||||
pthread_mutex_unlock (&gdns_thread.mutex);
|
||||
}
|
||||
|
||||
/* Consumer - Once an IP has been resolved, add it to dwithe hostnames
|
||||
* hash structure. */
|
||||
static void
|
||||
dns_worker (void GO_UNUSED (*ptr_data)) {
|
||||
char *ip = NULL, *host = NULL;
|
||||
|
||||
while (1) {
|
||||
pthread_mutex_lock (&gdns_thread.mutex);
|
||||
/* wait until an item has been added to the queue */
|
||||
while (gqueue_empty (gdns_queue))
|
||||
pthread_cond_wait (&gdns_thread.not_empty, &gdns_thread.mutex);
|
||||
|
||||
ip = gqueue_dequeue (gdns_queue);
|
||||
|
||||
pthread_mutex_unlock (&gdns_thread.mutex);
|
||||
host = reverse_ip (ip);
|
||||
pthread_mutex_lock (&gdns_thread.mutex);
|
||||
|
||||
if (!active_gdns) {
|
||||
pthread_mutex_unlock (&gdns_thread.mutex);
|
||||
free (host);
|
||||
return;
|
||||
}
|
||||
|
||||
/* insert the corresponding IP -> hostname map */
|
||||
if (host != NULL && active_gdns) {
|
||||
ht_insert_hostname (ip, host);
|
||||
free (host);
|
||||
}
|
||||
|
||||
pthread_cond_signal (&gdns_thread.not_full);
|
||||
pthread_mutex_unlock (&gdns_thread.mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize queue and dns thread */
|
||||
void
|
||||
gdns_init (void) {
|
||||
gdns_queue = xmalloc (sizeof (GDnsQueue));
|
||||
gqueue_init (gdns_queue, QUEUE_SIZE);
|
||||
|
||||
if (pthread_cond_init (&(gdns_thread.not_empty), NULL))
|
||||
FATAL ("Failed init thread condition");
|
||||
|
||||
if (pthread_cond_init (&(gdns_thread.not_full), NULL))
|
||||
FATAL ("Failed init thread condition");
|
||||
|
||||
if (pthread_mutex_init (&(gdns_thread.mutex), NULL))
|
||||
FATAL ("Failed init thread mutex");
|
||||
}
|
||||
|
||||
/* Destroy (free) queue */
|
||||
void
|
||||
gdns_free_queue (void) {
|
||||
gqueue_destroy (gdns_queue);
|
||||
}
|
||||
|
||||
/* Create a DNS thread and make it active */
|
||||
void
|
||||
gdns_thread_create (void) {
|
||||
int th;
|
||||
|
||||
active_gdns = 1;
|
||||
th = pthread_create (&(gdns_thread.thread), NULL, (void *) &dns_worker, NULL);
|
||||
if (th)
|
||||
FATAL ("Return code from pthread_create(): %d", th);
|
||||
pthread_detach (gdns_thread.thread);
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GDNS_H_INCLUDED
|
||||
#define GDNS_H_INCLUDED
|
||||
|
||||
#define H_SIZE 1025
|
||||
#define QUEUE_SIZE 400
|
||||
|
||||
typedef struct GDnsThread_ {
|
||||
pthread_cond_t not_empty; /* not empty queue condition */
|
||||
pthread_cond_t not_full; /* not full queue condition */
|
||||
pthread_mutex_t mutex;
|
||||
pthread_t thread;
|
||||
} GDnsThread;
|
||||
|
||||
typedef struct GDnsQueue_ {
|
||||
int head; /* index to head of queue */
|
||||
int tail; /* index to tail of queue */
|
||||
int size; /* queue size */
|
||||
int capacity; /* length at most */
|
||||
char buffer[QUEUE_SIZE][H_SIZE]; /* data item */
|
||||
} GDnsQueue;
|
||||
|
||||
extern GDnsThread gdns_thread;
|
||||
|
||||
char *gqueue_dequeue (GDnsQueue * q);
|
||||
char *reverse_ip (char *str);
|
||||
int gqueue_empty (GDnsQueue * q);
|
||||
int gqueue_enqueue (GDnsQueue * q, const char *item);
|
||||
int gqueue_find (GDnsQueue * q, const char *item);
|
||||
int gqueue_full (GDnsQueue * q);
|
||||
int gqueue_size (GDnsQueue * q);
|
||||
void dns_resolver (char *addr);
|
||||
void gdns_free_queue (void);
|
||||
void gdns_init (void);
|
||||
void gdns_queue_free (void);
|
||||
void gdns_thread_create (void);
|
||||
void gqueue_destroy (GDnsQueue * q);
|
||||
void gqueue_init (GDnsQueue * q, int capacity);
|
||||
|
||||
#endif
|
||||
+610
@@ -0,0 +1,610 @@
|
||||
/**
|
||||
* geoip.c -- implementation of GeoIP (legacy)
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_LIBGEOIP
|
||||
#include <GeoIP.h>
|
||||
#include <GeoIPCity.h>
|
||||
#endif
|
||||
|
||||
#include "geoip1.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "util.h"
|
||||
|
||||
static GeoIP **geoips = NULL;
|
||||
static GeoIP *geo_location_data = NULL;
|
||||
static int db_cnt = 0;
|
||||
static int legacy_db = 0;
|
||||
|
||||
/* Determine if we have a valid geoip resource.
|
||||
*
|
||||
* If the geoip resource is NULL, 0 is returned.
|
||||
* If the geoip resource is valid and malloc'd, 1 is returned. */
|
||||
int
|
||||
is_geoip_resource (void) {
|
||||
return ((geoips && db_cnt) || (legacy_db && geo_location_data));
|
||||
}
|
||||
|
||||
/* Free up GeoIP resources */
|
||||
void
|
||||
geoip_free (void) {
|
||||
int idx = 0;
|
||||
|
||||
if (!is_geoip_resource ())
|
||||
return;
|
||||
|
||||
for (idx = 0; idx < db_cnt; idx++)
|
||||
GeoIP_delete (geoips[idx]);
|
||||
|
||||
if (legacy_db)
|
||||
GeoIP_delete (geo_location_data);
|
||||
|
||||
GeoIP_cleanup ();
|
||||
free (geoips);
|
||||
geoips = NULL;
|
||||
}
|
||||
|
||||
/* Open the given GeoLocation database and set its charset.
|
||||
*
|
||||
* On error, it aborts.
|
||||
* On success, a new geolocation structure is returned. */
|
||||
static GeoIP *
|
||||
geoip_open_db (const char *db) {
|
||||
GeoIP *geoip = GeoIP_open (db, GEOIP_MEMORY_CACHE);
|
||||
|
||||
if (geoip == NULL)
|
||||
return NULL;
|
||||
|
||||
GeoIP_set_charset (geoip, GEOIP_CHARSET_UTF8);
|
||||
LOG_DEBUG (("Opened legacy GeoIP database: %s\n", db));
|
||||
|
||||
return geoip;
|
||||
}
|
||||
|
||||
static int
|
||||
set_geoip_db_by_type (GeoIP *geoip, GO_GEOIP_DB type) {
|
||||
unsigned char rec = GeoIP_database_edition (geoip);
|
||||
|
||||
switch (rec) {
|
||||
case GEOIP_ASNUM_EDITION:
|
||||
if (type != TYPE_ASN)
|
||||
break;
|
||||
geo_location_data = geoip;
|
||||
return 0;
|
||||
case GEOIP_COUNTRY_EDITION:
|
||||
case GEOIP_COUNTRY_EDITION_V6:
|
||||
if (type != TYPE_COUNTRY && type != TYPE_CITY)
|
||||
break;
|
||||
geo_location_data = geoip;
|
||||
return 0;
|
||||
case GEOIP_CITY_EDITION_REV0:
|
||||
case GEOIP_CITY_EDITION_REV1:
|
||||
case GEOIP_CITY_EDITION_REV0_V6:
|
||||
case GEOIP_CITY_EDITION_REV1_V6:
|
||||
if (type != TYPE_CITY)
|
||||
break;
|
||||
geo_location_data = geoip;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
set_conf_by_type (GeoIP *geoip) {
|
||||
unsigned char rec = GeoIP_database_edition (geoip);
|
||||
|
||||
switch (rec) {
|
||||
case GEOIP_ASNUM_EDITION:
|
||||
conf.has_geoasn = 1;
|
||||
return 0;
|
||||
case GEOIP_COUNTRY_EDITION:
|
||||
case GEOIP_COUNTRY_EDITION_V6:
|
||||
conf.has_geocountry = 1;
|
||||
return 0;
|
||||
case GEOIP_CITY_EDITION_REV0:
|
||||
case GEOIP_CITY_EDITION_REV1:
|
||||
case GEOIP_CITY_EDITION_REV0_V6:
|
||||
case GEOIP_CITY_EDITION_REV1_V6:
|
||||
conf.has_geocountry = conf.has_geocity = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Look up for a database on our array and set it as our current handlers.
|
||||
* Note: this is not ideal, for now. However, legacy will go out of support at some point.
|
||||
*
|
||||
* On error or if no entry is found, 1 is returned.
|
||||
* On success, GeoIP struct is set as our handler and 0 is returned. */
|
||||
static int
|
||||
set_geoip_db (GO_GEOIP_DB type) {
|
||||
int idx = 0;
|
||||
|
||||
if (!is_geoip_resource ())
|
||||
return 1;
|
||||
/* in memory legacy DB */
|
||||
if (legacy_db && geo_location_data)
|
||||
return 0;
|
||||
|
||||
for (idx = 0; idx < db_cnt; idx++) {
|
||||
if (set_geoip_db_by_type (geoips[idx], type) == 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
set_geoip (const char *db) {
|
||||
GeoIP **new_geoips = NULL, *geoip = NULL;
|
||||
|
||||
if (db == NULL || *db == '\0')
|
||||
return;
|
||||
|
||||
if (!(geoip = geoip_open_db (db)))
|
||||
FATAL ("Unable to open GeoIP database %s\n", db);
|
||||
|
||||
db_cnt++;
|
||||
|
||||
new_geoips = realloc (geoips, sizeof (*geoips) * db_cnt);
|
||||
if (new_geoips == NULL)
|
||||
FATAL ("Unable to realloc GeoIP database %s\n", db);
|
||||
geoips = new_geoips;
|
||||
geoips[db_cnt - 1] = geoip;
|
||||
|
||||
set_conf_by_type (geoip);
|
||||
}
|
||||
|
||||
/* Set up and open GeoIP database */
|
||||
void
|
||||
init_geoip (void) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < conf.geoip_db_idx; ++i)
|
||||
set_geoip (conf.geoip_databases[i]);
|
||||
|
||||
/* fall back to legacy GeoIP database */
|
||||
if (!conf.geoip_db_idx) {
|
||||
geo_location_data = GeoIP_new (conf.geo_db);
|
||||
legacy_db = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static char ip4to6_out_buffer[17];
|
||||
static char *
|
||||
ip4to6 (const char *ipv4) {
|
||||
unsigned int b[4];
|
||||
int n = sscanf (ipv4, "%u.%u.%u.%u", b, b + 1, b + 2, b + 3);
|
||||
if (n == 4) {
|
||||
snprintf (ip4to6_out_buffer, sizeof (ip4to6_out_buffer), "::ffff:%02x%02x:%02x%02x", b[0],
|
||||
b[1], b[2], b[3]);
|
||||
return ip4to6_out_buffer;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get continent name concatenated with code.
|
||||
*
|
||||
* If continent not found, "Unknown" is returned.
|
||||
* On success, the continent code & name is returned . */
|
||||
static const char *
|
||||
get_continent_name_and_code (const char *continentid) {
|
||||
if (memcmp (continentid, "NA", 2) == 0)
|
||||
return "NA North America";
|
||||
else if (memcmp (continentid, "OC", 2) == 0)
|
||||
return "OC Oceania";
|
||||
else if (memcmp (continentid, "EU", 2) == 0)
|
||||
return "EU Europe";
|
||||
else if (memcmp (continentid, "SA", 2) == 0)
|
||||
return "SA South America";
|
||||
else if (memcmp (continentid, "AF", 2) == 0)
|
||||
return "AF Africa";
|
||||
else if (memcmp (continentid, "AN", 2) == 0)
|
||||
return "AN Antarctica";
|
||||
else if (memcmp (continentid, "AS", 2) == 0)
|
||||
return "AS Asia";
|
||||
else
|
||||
return "-- Unknown";
|
||||
}
|
||||
|
||||
/* Compose a string with the country name and code and store it in the
|
||||
* given buffer. */
|
||||
static void
|
||||
geoip_set_country (const char *country, const char *code, char *loc) {
|
||||
if (country && code)
|
||||
snprintf (loc, COUNTRY_LEN, "%s %s", code, country);
|
||||
else
|
||||
snprintf (loc, COUNTRY_LEN, "%s", "Unknown");
|
||||
}
|
||||
|
||||
/* Compose a string with the city name and state/region and store it
|
||||
* in the given buffer. */
|
||||
static void
|
||||
geoip_set_city (const char *city, const char *region, char *loc) {
|
||||
snprintf (loc, CITY_LEN, "%s, %s", city ? city : "N/A City", region ? region : "N/A Region");
|
||||
}
|
||||
|
||||
/* Compose a string with the continent name and store it in the given
|
||||
* buffer. */
|
||||
static void
|
||||
geoip_set_continent (const char *continent, char *loc) {
|
||||
if (continent)
|
||||
snprintf (loc, CONTINENT_LEN, "%s", get_continent_name_and_code (continent));
|
||||
else
|
||||
snprintf (loc, CONTINENT_LEN, "%s", "Unknown");
|
||||
}
|
||||
|
||||
/* Compose a string with the continent name and store it in the given
|
||||
* buffer. */
|
||||
static void
|
||||
geoip_set_asn (const char *name, char *asn) {
|
||||
if (name)
|
||||
snprintf (asn, ASN_LEN, "%s", name);
|
||||
else
|
||||
snprintf (asn, ASN_LEN, "%s", "Unknown");
|
||||
}
|
||||
|
||||
/* Get detailed information found in the GeoIP Database about the
|
||||
* given IPv4 or IPv6.
|
||||
*
|
||||
* On error, NULL is returned
|
||||
* On success, GeoIPRecord structure is returned */
|
||||
static GeoIPRecord *
|
||||
get_geoip_record (const char *addr, GTypeIP type_ip) {
|
||||
GeoIPRecord *rec = NULL;
|
||||
|
||||
if (TYPE_IPV4 == type_ip)
|
||||
rec = GeoIP_record_by_name (geo_location_data, addr);
|
||||
else if (TYPE_IPV6 == type_ip)
|
||||
rec = GeoIP_record_by_name_v6 (geo_location_data, addr);
|
||||
|
||||
return rec;
|
||||
}
|
||||
|
||||
/* Set country data by record into the given `location` buffer based
|
||||
* on the IP version. */
|
||||
static void
|
||||
geoip_set_country_by_record (const char *ip, char *location, GTypeIP type_ip) {
|
||||
GeoIPRecord *rec = NULL;
|
||||
const char *country = NULL, *code = NULL, *addr = ip;
|
||||
|
||||
if (geo_location_data == NULL)
|
||||
return;
|
||||
|
||||
/* Custom GeoIP database */
|
||||
if ((rec = get_geoip_record (addr, type_ip))) {
|
||||
country = rec->country_name;
|
||||
code = rec->country_code;
|
||||
}
|
||||
|
||||
geoip_set_country (country, code, location);
|
||||
if (rec != NULL) {
|
||||
GeoIPRecord_delete (rec);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the GeoIP location id by name.
|
||||
*
|
||||
* On error, 0 is returned
|
||||
* On success, the GeoIP location id is returned */
|
||||
static int
|
||||
geoip_get_geoid (const char *addr, GTypeIP type_ip) {
|
||||
int geoid = 0;
|
||||
|
||||
if (TYPE_IPV4 == type_ip)
|
||||
geoid = GeoIP_id_by_name (geo_location_data, addr);
|
||||
else if (TYPE_IPV6 == type_ip)
|
||||
geoid = GeoIP_id_by_name_v6 (geo_location_data, addr);
|
||||
|
||||
return geoid;
|
||||
}
|
||||
|
||||
/* Get the country name by GeoIP location id.
|
||||
*
|
||||
* On error, NULL is returned
|
||||
* On success, the country name is returned */
|
||||
static const char *
|
||||
geoip_get_country_by_geoid (const char *addr, GTypeIP type_ip) {
|
||||
const char *country = NULL;
|
||||
|
||||
if (TYPE_IPV4 == type_ip)
|
||||
country = GeoIP_country_name_by_name (geo_location_data, addr);
|
||||
else if (TYPE_IPV6 == type_ip)
|
||||
country = GeoIP_country_name_by_name_v6 (geo_location_data, addr);
|
||||
|
||||
return country;
|
||||
}
|
||||
|
||||
/* Set country data by geoid into the given `location` buffer based on
|
||||
* the IP version. */
|
||||
static void
|
||||
geoip_set_country_by_geoid (const char *ip, char *location, GTypeIP type_ip) {
|
||||
const char *country = NULL, *code = NULL, *addr = ip;
|
||||
int geoid = 0;
|
||||
|
||||
if (!is_geoip_resource ())
|
||||
return;
|
||||
|
||||
if (!(country = geoip_get_country_by_geoid (addr, type_ip)))
|
||||
goto out;
|
||||
|
||||
/* return two letter country code */
|
||||
if (!(geoid = geoip_get_geoid (addr, type_ip)))
|
||||
goto out;
|
||||
code = GeoIP_code_by_id (geoid);
|
||||
|
||||
out:
|
||||
geoip_set_country (country, code, location);
|
||||
}
|
||||
|
||||
/* Set continent data by record into the given `location` buffer based
|
||||
* on the IP version. */
|
||||
static void
|
||||
geoip_set_continent_by_record (const char *ip, char *location, GTypeIP type_ip) {
|
||||
GeoIPRecord *rec = NULL;
|
||||
const char *continent = NULL, *addr = ip;
|
||||
|
||||
if (geo_location_data == NULL)
|
||||
return;
|
||||
|
||||
/* Custom GeoIP database */
|
||||
if ((rec = get_geoip_record (addr, type_ip)))
|
||||
continent = rec->continent_code;
|
||||
|
||||
geoip_set_continent (continent, location);
|
||||
if (rec != NULL) {
|
||||
GeoIPRecord_delete (rec);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set continent data by geoid into the given `location` buffer based
|
||||
* on the IP version. */
|
||||
static void
|
||||
geoip_set_continent_by_geoid (const char *ip, char *location, GTypeIP type_ip) {
|
||||
const char *continent = NULL, *addr = ip;
|
||||
int geoid = 0;
|
||||
|
||||
if (!is_geoip_resource ())
|
||||
return;
|
||||
|
||||
if (!(geoid = geoip_get_geoid (addr, type_ip)))
|
||||
goto out;
|
||||
continent = GeoIP_continent_by_id (geoid);
|
||||
|
||||
out:
|
||||
geoip_set_continent (continent, location);
|
||||
}
|
||||
|
||||
/* Set city data by record into the given `location` buffer based on
|
||||
* the IP version. */
|
||||
static void
|
||||
geoip_set_city_by_record (const char *ip, char *location, GTypeIP type_ip) {
|
||||
GeoIPRecord *rec = NULL;
|
||||
const char *city = NULL, *region = NULL, *addr = ip;
|
||||
|
||||
/* Custom GeoIP database */
|
||||
if ((rec = get_geoip_record (addr, type_ip))) {
|
||||
city = rec->city;
|
||||
region = rec->region;
|
||||
}
|
||||
|
||||
geoip_set_city (city, region, location);
|
||||
if (rec != NULL) {
|
||||
GeoIPRecord_delete (rec);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set city data by geoid or record into the given `location` buffer
|
||||
* based on the IP version and currently used database edition.
|
||||
* It uses the custom GeoIP database - i.e., GeoLiteCity.dat */
|
||||
static void
|
||||
geoip_get_city (const char *ip, char *location, GTypeIP type_ip) {
|
||||
unsigned char rec = 0;
|
||||
|
||||
if (geo_location_data == NULL)
|
||||
return;
|
||||
|
||||
rec = GeoIP_database_edition (geo_location_data);
|
||||
switch (rec) {
|
||||
case GEOIP_CITY_EDITION_REV0:
|
||||
case GEOIP_CITY_EDITION_REV1:
|
||||
if (TYPE_IPV4 == type_ip)
|
||||
geoip_set_city_by_record (ip, location, TYPE_IPV4);
|
||||
else
|
||||
geoip_set_city (NULL, NULL, location);
|
||||
break;
|
||||
case GEOIP_CITY_EDITION_REV0_V6:
|
||||
case GEOIP_CITY_EDITION_REV1_V6:
|
||||
if (TYPE_IPV6 == type_ip)
|
||||
geoip_set_city_by_record (ip, location, TYPE_IPV6);
|
||||
else {
|
||||
char *ipv6 = ip4to6 (ip);
|
||||
if (ipv6)
|
||||
geoip_set_city_by_record (ipv6, location, TYPE_IPV6);
|
||||
else
|
||||
geoip_set_city (NULL, NULL, location);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set country data by geoid or record into the given `location` buffer
|
||||
* based on the IP version and currently used database edition. */
|
||||
void
|
||||
geoip_get_country (const char *ip, char *location, GTypeIP type_ip) {
|
||||
unsigned char rec = 0;
|
||||
|
||||
if (set_geoip_db (TYPE_COUNTRY) && set_geoip_db (TYPE_CITY)) {
|
||||
geoip_set_country (NULL, NULL, location);
|
||||
return;
|
||||
}
|
||||
|
||||
rec = GeoIP_database_edition (geo_location_data);
|
||||
switch (rec) {
|
||||
case GEOIP_COUNTRY_EDITION:
|
||||
if (TYPE_IPV4 == type_ip)
|
||||
geoip_set_country_by_geoid (ip, location, TYPE_IPV4);
|
||||
else
|
||||
geoip_set_country (NULL, NULL, location);
|
||||
break;
|
||||
case GEOIP_COUNTRY_EDITION_V6:
|
||||
if (TYPE_IPV6 == type_ip)
|
||||
geoip_set_country_by_geoid (ip, location, TYPE_IPV6);
|
||||
else {
|
||||
char *ipv6 = ip4to6 (ip);
|
||||
if (ipv6)
|
||||
geoip_set_country_by_geoid (ipv6, location, TYPE_IPV6);
|
||||
else
|
||||
geoip_set_country (NULL, NULL, location);
|
||||
}
|
||||
break;
|
||||
case GEOIP_CITY_EDITION_REV0:
|
||||
case GEOIP_CITY_EDITION_REV1:
|
||||
if (TYPE_IPV4 == type_ip)
|
||||
geoip_set_country_by_record (ip, location, TYPE_IPV4);
|
||||
else
|
||||
geoip_set_country (NULL, NULL, location);
|
||||
break;
|
||||
case GEOIP_CITY_EDITION_REV0_V6:
|
||||
case GEOIP_CITY_EDITION_REV1_V6:
|
||||
if (TYPE_IPV6 == type_ip)
|
||||
geoip_set_country_by_record (ip, location, TYPE_IPV6);
|
||||
else {
|
||||
char *ipv6 = ip4to6 (ip);
|
||||
if (ipv6)
|
||||
geoip_set_country_by_record (ipv6, location, TYPE_IPV6);
|
||||
else
|
||||
geoip_set_country (NULL, NULL, location);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set continent data by geoid or record into the given `location` buffer
|
||||
* based on the IP version and currently used database edition. */
|
||||
void
|
||||
geoip_get_continent (const char *ip, char *location, GTypeIP type_ip) {
|
||||
unsigned char rec = 0;
|
||||
|
||||
if (set_geoip_db (TYPE_COUNTRY) && set_geoip_db (TYPE_CITY)) {
|
||||
geoip_set_continent (NULL, location);
|
||||
return;
|
||||
}
|
||||
|
||||
rec = GeoIP_database_edition (geo_location_data);
|
||||
switch (rec) {
|
||||
case GEOIP_COUNTRY_EDITION:
|
||||
if (TYPE_IPV4 == type_ip)
|
||||
geoip_set_continent_by_geoid (ip, location, TYPE_IPV4);
|
||||
else
|
||||
geoip_set_continent (NULL, location);
|
||||
break;
|
||||
case GEOIP_COUNTRY_EDITION_V6:
|
||||
if (TYPE_IPV6 == type_ip)
|
||||
geoip_set_continent_by_geoid (ip, location, TYPE_IPV6);
|
||||
else {
|
||||
char *ipv6 = ip4to6 (ip);
|
||||
if (ipv6)
|
||||
geoip_set_continent_by_geoid (ipv6, location, TYPE_IPV6);
|
||||
else
|
||||
geoip_set_continent (NULL, location);
|
||||
}
|
||||
break;
|
||||
case GEOIP_CITY_EDITION_REV0:
|
||||
case GEOIP_CITY_EDITION_REV1:
|
||||
if (TYPE_IPV4 == type_ip)
|
||||
geoip_set_continent_by_record (ip, location, TYPE_IPV4);
|
||||
else
|
||||
geoip_set_continent (NULL, location);
|
||||
break;
|
||||
case GEOIP_CITY_EDITION_REV0_V6:
|
||||
case GEOIP_CITY_EDITION_REV1_V6:
|
||||
if (TYPE_IPV6 == type_ip)
|
||||
geoip_set_continent_by_record (ip, location, TYPE_IPV6);
|
||||
else {
|
||||
char *ipv6 = ip4to6 (ip);
|
||||
if (ipv6)
|
||||
geoip_set_continent_by_record (ipv6, location, TYPE_IPV6);
|
||||
else
|
||||
geoip_set_continent (NULL, location);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
geoip_asn (char *host, char *asn) {
|
||||
char *name = NULL;
|
||||
|
||||
if (legacy_db || set_geoip_db (TYPE_ASN)) {
|
||||
geoip_set_asn (NULL, asn);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Custom GeoIP database */
|
||||
name = GeoIP_org_by_name (geo_location_data, (const char *) host);
|
||||
geoip_set_asn (name, asn);
|
||||
free (name);
|
||||
}
|
||||
|
||||
/* Entry point to set GeoIP location into the corresponding buffers,
|
||||
* (continent, country, city).
|
||||
*
|
||||
* On error, 1 is returned
|
||||
* On success, buffers are set and 0 is returned */
|
||||
int
|
||||
set_geolocation (char *host, char *continent, char *country, char *city, GO_UNUSED char *asn) {
|
||||
int type_ip = 0;
|
||||
|
||||
if (!is_geoip_resource ())
|
||||
return 1;
|
||||
|
||||
if (invalid_ipaddr (host, &type_ip))
|
||||
return 1;
|
||||
|
||||
/* set ASN data */
|
||||
geoip_asn (host, asn);
|
||||
|
||||
/* set Country/City data */
|
||||
if (set_geoip_db (TYPE_COUNTRY) == 0 || set_geoip_db (TYPE_CITY) == 0) {
|
||||
geoip_get_country (host, country, type_ip);
|
||||
geoip_get_continent (host, continent, type_ip);
|
||||
}
|
||||
if (set_geoip_db (TYPE_CITY) == 0)
|
||||
geoip_get_city (host, city, type_ip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifndef GEOIP_H_INCLUDED
|
||||
#define GEOIP_H_INCLUDED
|
||||
|
||||
#include "commons.h"
|
||||
|
||||
#define CITY_LEN 47 + 1 /* max string length for a city */
|
||||
#define CONTINENT_LEN 47 + 1 /* max string length for a country */
|
||||
#define COUNTRY_LEN 48 + 3 /* Country + two-letter Code */
|
||||
#define ASN_LEN 64 + 6 /* ASN + 5 digit/16-bit number/code */
|
||||
|
||||
/* Type of IP */
|
||||
typedef enum {
|
||||
TYPE_COUNTRY,
|
||||
TYPE_CITY,
|
||||
TYPE_ASN
|
||||
} GO_GEOIP_DB;
|
||||
|
||||
typedef struct GLocation_ {
|
||||
char city[CITY_LEN];
|
||||
char continent[CONTINENT_LEN];
|
||||
int hits;
|
||||
} GLocation;
|
||||
|
||||
int is_geoip_resource (void);
|
||||
int set_geolocation (char *host, char *continent, char *country, char *city, char *asn);
|
||||
void geoip_asn (char *host, char *asn);
|
||||
void geoip_free (void);
|
||||
void geoip_get_continent (const char *ip, char *location, GTypeIP type_ip);
|
||||
void geoip_get_country (const char *ip, char *location, GTypeIP type_ip);
|
||||
void init_geoip (void);
|
||||
|
||||
#endif // for #ifndef GEOIP_H
|
||||
|
||||
void geoip_get_latlng (const char *host, double *lat, double *lng);
|
||||
+433
@@ -0,0 +1,433 @@
|
||||
/**
|
||||
* geoip2.c -- implementation of GeoIP2
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2020 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef HAVE_LIBMAXMINDDB
|
||||
#include <maxminddb.h>
|
||||
#endif
|
||||
|
||||
#include "geoip1.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "labels.h"
|
||||
#include "util.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
/* should be reused across lookups */
|
||||
static int geoip_asn_type = 0;
|
||||
static int geoip_city_type = 0;
|
||||
static int geoip_country_type = 0;
|
||||
static int mmdb_cnt = 0;
|
||||
static MMDB_s *mmdbs = NULL;
|
||||
|
||||
/* Determine if we have a valid geoip resource.
|
||||
*
|
||||
* If the geoip resource is NULL, 0 is returned.
|
||||
* If the geoip resource is valid and malloc'd, 1 is returned. */
|
||||
int
|
||||
is_geoip_resource (void) {
|
||||
return mmdbs && mmdb_cnt;
|
||||
}
|
||||
|
||||
/* Free up GeoIP resources */
|
||||
void
|
||||
geoip_free (void) {
|
||||
int idx = 0;
|
||||
if (!is_geoip_resource ())
|
||||
return;
|
||||
|
||||
for (idx = 0; idx < mmdb_cnt; idx++)
|
||||
MMDB_close (&mmdbs[idx]);
|
||||
free (mmdbs);
|
||||
mmdbs = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
set_geoip (const char *db) {
|
||||
int status = 0;
|
||||
MMDB_s *new_mmdbs = NULL, mmdb;
|
||||
|
||||
if (db == NULL || *db == '\0')
|
||||
return;
|
||||
|
||||
if ((status = MMDB_open (db, MMDB_MODE_MMAP, &mmdb)) != MMDB_SUCCESS)
|
||||
FATAL ("Unable to open GeoIP2 database %s: %s\n", db, MMDB_strerror (status));
|
||||
|
||||
mmdb_cnt++;
|
||||
|
||||
new_mmdbs = realloc (mmdbs, sizeof (*mmdbs) * mmdb_cnt);
|
||||
if (new_mmdbs == NULL)
|
||||
FATAL ("Unable to realloc GeoIP2 database %s\n", db);
|
||||
mmdbs = new_mmdbs;
|
||||
mmdbs[mmdb_cnt - 1] = mmdb;
|
||||
|
||||
if (strstr (mmdb.metadata.database_type, "-City") != NULL)
|
||||
conf.has_geocountry = conf.has_geocity = geoip_country_type = geoip_city_type = 1;
|
||||
if (strstr (mmdb.metadata.database_type, "-ASN") != NULL)
|
||||
conf.has_geoasn = geoip_asn_type = 1;
|
||||
if (strstr (mmdb.metadata.database_type, "-Country") != NULL)
|
||||
conf.has_geocountry = geoip_country_type = 1;
|
||||
}
|
||||
|
||||
/* Open the given GeoIP2 database.
|
||||
*
|
||||
* On error, it aborts.
|
||||
* On success, a new geolocation structure is set. */
|
||||
void
|
||||
init_geoip (void) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < conf.geoip_db_idx; ++i)
|
||||
set_geoip (conf.geoip_databases[i]);
|
||||
}
|
||||
|
||||
/* Look up an IP address that is passed in as a null-terminated string.
|
||||
*
|
||||
* On error, it aborts.
|
||||
* If no entry is found, 1 is returned.
|
||||
* On success, MMDB_lookup_result_s struct is set and 0 is returned. */
|
||||
static int
|
||||
geoip_lookup (MMDB_lookup_result_s *res, const char *ip, int is_asn) {
|
||||
int gai_err, mmdb_err, idx = 0;
|
||||
MMDB_s *mmdb = NULL;
|
||||
|
||||
for (idx = 0; idx < mmdb_cnt; idx++) {
|
||||
if (is_asn && (!strstr (mmdbs[idx].metadata.database_type, "ASN")))
|
||||
continue;
|
||||
if (!is_asn && (strstr (mmdbs[idx].metadata.database_type, "ASN")))
|
||||
continue;
|
||||
|
||||
mmdb = &mmdbs[idx];
|
||||
|
||||
*res = MMDB_lookup_string (mmdb, ip, &gai_err, &mmdb_err);
|
||||
if (0 != gai_err)
|
||||
return 1;
|
||||
if (MMDB_SUCCESS != mmdb_err)
|
||||
FATAL ("Error from libmaxminddb: %s\n", MMDB_strerror (mmdb_err));
|
||||
if (!(*res).found_entry)
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get continent name concatenated with code.
|
||||
*
|
||||
* If continent not found, "Unknown" is returned.
|
||||
* On success, the continent code & name is returned . */
|
||||
static const char *
|
||||
get_continent_name_and_code (const char *continentid) {
|
||||
if (memcmp (continentid, "NA", 2) == 0)
|
||||
return "NA North America";
|
||||
else if (memcmp (continentid, "OC", 2) == 0)
|
||||
return "OC Oceania";
|
||||
else if (memcmp (continentid, "EU", 2) == 0)
|
||||
return "EU Europe";
|
||||
else if (memcmp (continentid, "SA", 2) == 0)
|
||||
return "SA South America";
|
||||
else if (memcmp (continentid, "AF", 2) == 0)
|
||||
return "AF Africa";
|
||||
else if (memcmp (continentid, "AN", 2) == 0)
|
||||
return "AN Antarctica";
|
||||
else if (memcmp (continentid, "AS", 2) == 0)
|
||||
return "AS Asia";
|
||||
else
|
||||
return "-- Unknown";
|
||||
}
|
||||
|
||||
/* Compose a string with the country name and code and store it in the
|
||||
* given buffer. */
|
||||
static void
|
||||
geoip_set_country (const char *country, const char *code, char *loc) {
|
||||
if (country && code)
|
||||
snprintf (loc, COUNTRY_LEN, "%s %s", code, country);
|
||||
else
|
||||
snprintf (loc, COUNTRY_LEN, "%s", "Unknown");
|
||||
}
|
||||
|
||||
/* Compose a string with the ASN name and code and store it in the
|
||||
* given buffer. */
|
||||
static void
|
||||
geoip_set_asn (MMDB_entry_data_s name, MMDB_entry_data_s code, char *asn, int status) {
|
||||
if (status == 0)
|
||||
snprintf (asn, ASN_LEN, "%05u: %.*s", code.uint32, name.data_size, name.utf8_string);
|
||||
else
|
||||
snprintf (asn, ASN_LEN, "%s", "00000: Unknown");
|
||||
}
|
||||
|
||||
/* Compose a string with the city name and state/region and store it
|
||||
* in the given buffer. */
|
||||
static void
|
||||
geoip_set_city (const char *city, char *loc) {
|
||||
snprintf (loc, CITY_LEN, "%s", city ? city : "N/A City");
|
||||
}
|
||||
|
||||
/* Compose a string with the continent name and store it in the given
|
||||
* buffer. */
|
||||
static void
|
||||
geoip_set_continent (const char *continent, char *loc) {
|
||||
if (continent)
|
||||
snprintf (loc, CONTINENT_LEN, "%s", get_continent_name_and_code (continent));
|
||||
else
|
||||
snprintf (loc, CONTINENT_LEN, "%s", "Unknown");
|
||||
}
|
||||
|
||||
/* A wrapper to fetch the looked up result set.
|
||||
*
|
||||
* On error, it aborts.
|
||||
* If no data is found, NULL is returned.
|
||||
* On success, the fetched value is returned. */
|
||||
static char *
|
||||
get_value (MMDB_lookup_result_s res, ...) {
|
||||
MMDB_entry_data_s entry_data;
|
||||
char *value = NULL;
|
||||
int status = 0;
|
||||
va_list keys;
|
||||
va_start (keys, res);
|
||||
|
||||
status = MMDB_vget_value (&res.entry, &entry_data, keys);
|
||||
va_end (keys);
|
||||
|
||||
if (status != MMDB_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
if (!entry_data.has_data)
|
||||
return NULL;
|
||||
|
||||
if (entry_data.type != MMDB_DATA_TYPE_UTF8_STRING)
|
||||
FATAL ("Invalid data UTF8 GeoIP2 data %d:\n", entry_data.type);
|
||||
|
||||
if ((value = strndup (entry_data.utf8_string, entry_data.data_size)) == NULL)
|
||||
FATAL ("Unable to allocate buffer %s: ", strerror (errno));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* A wrapper to fetch the looked up result and set the city and region.
|
||||
*
|
||||
* If no data is found, NULL is set.
|
||||
* On success, the fetched value is set. */
|
||||
static void
|
||||
geoip_query_city (MMDB_lookup_result_s res, char *location) {
|
||||
char *city = NULL, *region = NULL;
|
||||
|
||||
if (res.found_entry) {
|
||||
city = get_value (res, "city", "names", DOC_LANG, NULL);
|
||||
region = get_value (res, "subdivisions", "0", "names", DOC_LANG, NULL);
|
||||
if (!city) {
|
||||
city = get_value (res, "city", "names", "en", NULL);
|
||||
}
|
||||
if (!region) {
|
||||
region = get_value (res, "subdivisions", "0", "names", "en", NULL);
|
||||
}
|
||||
}
|
||||
geoip_set_city (city, location);
|
||||
free (city);
|
||||
free (region);
|
||||
}
|
||||
|
||||
/* A wrapper to fetch the looked up result and set the country and code.
|
||||
*
|
||||
* If no data is found, NULL is set.
|
||||
* On success, the fetched value is set. */
|
||||
static void
|
||||
geoip_query_country (MMDB_lookup_result_s res, char *location) {
|
||||
char *country = NULL, *code = NULL;
|
||||
|
||||
if (res.found_entry) {
|
||||
country = get_value (res, "country", "names", DOC_LANG, NULL);
|
||||
code = get_value (res, "country", "iso_code", NULL);
|
||||
if (!country) {
|
||||
country = get_value (res, "country", "names", "en", NULL);
|
||||
}
|
||||
}
|
||||
geoip_set_country (country, code, location);
|
||||
free (code);
|
||||
free (country);
|
||||
}
|
||||
|
||||
/* Sets the value for the given array of strings as the lookup path.
|
||||
*
|
||||
* On error or not found, 1 is returned.
|
||||
* On success, 0 is returned and the entry data is set. */
|
||||
static int
|
||||
geoip_query_asn_code (MMDB_lookup_result_s res, MMDB_entry_data_s *code) {
|
||||
int status;
|
||||
const char *key[] = { "autonomous_system_number", NULL };
|
||||
|
||||
if (res.found_entry) {
|
||||
status = MMDB_aget_value (&res.entry, code, key);
|
||||
if (status != MMDB_SUCCESS || !code->has_data)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Sets the value for the given array of strings as the lookup path.
|
||||
*
|
||||
* On error or not found, 1 is returned.
|
||||
* On success, 0 is returned and the entry data is set. */
|
||||
static int
|
||||
geoip_query_asn_name (MMDB_lookup_result_s res, MMDB_entry_data_s *name) {
|
||||
int status;
|
||||
const char *key[] = { "autonomous_system_organization", NULL };
|
||||
|
||||
if (res.found_entry) {
|
||||
status = MMDB_aget_value (&res.entry, name, key);
|
||||
if (status != MMDB_SUCCESS || !name->has_data)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* A wrapper to fetch the looked up result and set the ASN organization & code.
|
||||
*
|
||||
* If no data is found, "Unknown" is set.
|
||||
* On success, the fetched value is set. */
|
||||
void
|
||||
geoip_asn (char *host, char *asn) {
|
||||
MMDB_lookup_result_s res = { 0 };
|
||||
MMDB_entry_data_s name = { 0 };
|
||||
MMDB_entry_data_s code = { 0 };
|
||||
int status = 1;
|
||||
|
||||
geoip_lookup (&res, host, 1);
|
||||
|
||||
if (!res.found_entry)
|
||||
goto out;
|
||||
if ((status &= geoip_query_asn_name (res, &name)))
|
||||
goto out;
|
||||
if ((status |= geoip_query_asn_code (res, &code)))
|
||||
goto out;
|
||||
|
||||
out:
|
||||
geoip_set_asn (name, code, asn, status);
|
||||
}
|
||||
|
||||
/* A wrapper to fetch the looked up result and set the continent code.
|
||||
*
|
||||
* If no data is found, NULL is set.
|
||||
* On success, the fetched value is set. */
|
||||
static void
|
||||
geoip_query_continent (MMDB_lookup_result_s res, char *location) {
|
||||
char *code = NULL;
|
||||
|
||||
if (res.found_entry)
|
||||
code = get_value (res, "continent", "code", NULL);
|
||||
geoip_set_continent (code, location);
|
||||
free (code);
|
||||
}
|
||||
|
||||
/* Set country data by record into the given `location` buffer */
|
||||
void
|
||||
geoip_get_country (const char *ip, char *location, GO_UNUSED GTypeIP type_ip) {
|
||||
MMDB_lookup_result_s res = { 0 };
|
||||
|
||||
geoip_lookup (&res, ip, 0);
|
||||
geoip_query_country (res, location);
|
||||
}
|
||||
|
||||
/* A wrapper to fetch the looked up result and set the continent. */
|
||||
void
|
||||
geoip_get_continent (const char *ip, char *location, GO_UNUSED GTypeIP type_ip) {
|
||||
MMDB_lookup_result_s res = { 0 };
|
||||
|
||||
geoip_lookup (&res, ip, 0);
|
||||
geoip_query_continent (res, location);
|
||||
}
|
||||
|
||||
/* Extract lat/lng coordinates from mmdb for the given host. */
|
||||
static void
|
||||
geoip_get_coordinates (const char *host, double *lat, double *lng) {
|
||||
MMDB_lookup_result_s res = { 0 };
|
||||
MMDB_entry_data_s entry_data;
|
||||
int status;
|
||||
const char *lat_keys[] = {"location", "latitude", NULL};
|
||||
const char *lng_keys[] = {"location", "longitude", NULL};
|
||||
|
||||
*lat = *lng = 0.0;
|
||||
geoip_lookup (&res, host, 0);
|
||||
if (!res.found_entry)
|
||||
return;
|
||||
|
||||
status = MMDB_aget_value (&res.entry, &entry_data, lat_keys);
|
||||
if (status == MMDB_SUCCESS && entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_DOUBLE)
|
||||
*lat = entry_data.double_value;
|
||||
|
||||
status = MMDB_aget_value (&res.entry, &entry_data, lng_keys);
|
||||
if (status == MMDB_SUCCESS && entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_DOUBLE)
|
||||
*lng = entry_data.double_value;
|
||||
}
|
||||
|
||||
/* Get lat/lng for a given IP (public wrapper). */
|
||||
void
|
||||
geoip_get_latlng (const char *host, double *lat, double *lng) {
|
||||
geoip_get_coordinates (host, lat, lng);
|
||||
}
|
||||
|
||||
/* Entry point to set GeoIP location into the corresponding buffers,
|
||||
* (continent, country, city).
|
||||
*
|
||||
* On error, 1 is returned
|
||||
* On success, buffers are set and 0 is returned */
|
||||
int
|
||||
set_geolocation (char *host, char *continent, char *country, char *city, char *asn) {
|
||||
MMDB_lookup_result_s res = { 0 };
|
||||
|
||||
if (!is_geoip_resource ())
|
||||
return 1;
|
||||
|
||||
/* set ASN data */
|
||||
if (geoip_asn_type)
|
||||
geoip_asn (host, asn);
|
||||
|
||||
if (!geoip_city_type && !geoip_country_type)
|
||||
return 0;
|
||||
|
||||
/* set Country/City data */
|
||||
geoip_lookup (&res, host, 0);
|
||||
geoip_query_country (res, country);
|
||||
geoip_query_continent (res, continent);
|
||||
|
||||
if (geoip_city_type)
|
||||
geoip_query_city (res, city);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+897
@@ -0,0 +1,897 @@
|
||||
/**
|
||||
* gholder.c -- data structure to hold raw data
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "gholder.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "gdns.h"
|
||||
#include "gkhash.h"
|
||||
#include "gstorage.h"
|
||||
#include "util.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
#include "geoip1.h"
|
||||
#endif
|
||||
|
||||
typedef struct GPanel_ {
|
||||
GModule module;
|
||||
void (*insert) (GRawDataItem item, GHolder * h, datatype type, const struct GPanel_ *);
|
||||
void (*holder_callback) (GHolder * h);
|
||||
} GPanel;
|
||||
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
static void add_data_to_holder (GRawDataItem item, GHolder * h, datatype type, const GPanel * panel);
|
||||
static void add_host_to_holder (GRawDataItem item, GHolder * h, datatype type, const GPanel * panel);
|
||||
static void add_root_to_holder (GRawDataItem item, GHolder * h, datatype type, const GPanel * panel);
|
||||
static void add_host_child_to_holder (GHolder * h);
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
static void add_geo_to_holder (GRawDataItem item, GHolder * h, datatype type, const GPanel * panel);
|
||||
#endif
|
||||
|
||||
static const GPanel paneling[] = {
|
||||
{VISITORS , add_data_to_holder , NULL},
|
||||
{REQUESTS , add_data_to_holder , NULL},
|
||||
{REQUESTS_STATIC , add_data_to_holder , NULL},
|
||||
{NOT_FOUND , add_data_to_holder , NULL},
|
||||
{HOSTS , add_host_to_holder , add_host_child_to_holder} ,
|
||||
{OS , add_root_to_holder , NULL},
|
||||
{BROWSERS , add_root_to_holder , NULL},
|
||||
{VISIT_TIMES , add_data_to_holder , NULL},
|
||||
{VIRTUAL_HOSTS , add_data_to_holder , NULL},
|
||||
{REFERRERS , add_data_to_holder , NULL},
|
||||
{REFERRING_SITES , add_data_to_holder , NULL},
|
||||
{KEYPHRASES , add_data_to_holder , NULL},
|
||||
{STATUS_CODES , add_root_to_holder , NULL},
|
||||
{REMOTE_USER , add_data_to_holder , NULL},
|
||||
{CACHE_STATUS , add_data_to_holder , NULL},
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
{GEO_LOCATION , add_geo_to_holder , NULL},
|
||||
{ASN , add_data_to_holder , NULL} ,
|
||||
#endif
|
||||
{MIME_TYPE , add_root_to_holder , NULL} ,
|
||||
{TLS_TYPE , add_root_to_holder , NULL} ,
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
|
||||
/* Get a panel from the GPanel structure given a module.
|
||||
*
|
||||
* On error, or if not found, NULL is returned.
|
||||
* On success, the panel value is returned. */
|
||||
static const GPanel *
|
||||
panel_lookup (GModule module) {
|
||||
int i, num_panels = ARRAY_SIZE (paneling);
|
||||
|
||||
for (i = 0; i < num_panels; i++) {
|
||||
if (paneling[i].module == module)
|
||||
return &paneling[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocate memory for a new GHolder instance.
|
||||
*
|
||||
* On success, the newly allocated GHolder is returned . */
|
||||
GHolder *
|
||||
new_gholder (uint32_t size) {
|
||||
GHolder *holder = xmalloc (size * sizeof (GHolder));
|
||||
memset (holder, 0, size * sizeof *holder);
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
/* Allocate memory for a new GHolderItem instance.
|
||||
*
|
||||
* On success, the newly allocated GHolderItem is returned . */
|
||||
static GHolderItem *
|
||||
new_gholder_item (uint32_t size) {
|
||||
GHolderItem *item = xcalloc (size, sizeof (GHolderItem));
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/* Allocate memory for a new double linked-list GSubList instance.
|
||||
*
|
||||
* On success, the newly allocated GSubList is returned . */
|
||||
static GSubList *
|
||||
new_gsublist (void) {
|
||||
GSubList *sub_list = xmalloc (sizeof (GSubList));
|
||||
sub_list->head = NULL;
|
||||
sub_list->tail = NULL;
|
||||
sub_list->size = 0;
|
||||
|
||||
return sub_list;
|
||||
}
|
||||
|
||||
/* Allocate memory for a new double linked-list GSubItem node.
|
||||
*
|
||||
* On success, the newly allocated GSubItem is returned . */
|
||||
static GSubItem *
|
||||
new_gsubitem (GModule module, GMetrics *nmetrics) {
|
||||
GSubItem *sub_item = xmalloc (sizeof (GSubItem));
|
||||
|
||||
sub_item->metrics = nmetrics;
|
||||
sub_item->module = module;
|
||||
sub_item->sub_list = NULL;
|
||||
sub_item->prev = NULL;
|
||||
sub_item->next = NULL;
|
||||
|
||||
return sub_item;
|
||||
}
|
||||
|
||||
/* Add an item to the end of a given sub list. */
|
||||
static void
|
||||
add_sub_item_back (GSubList *sub_list, GModule module, GMetrics *nmetrics) {
|
||||
GSubItem *sub_item = new_gsubitem (module, nmetrics);
|
||||
if (sub_list->tail) {
|
||||
sub_list->tail->next = sub_item;
|
||||
sub_item->prev = sub_list->tail;
|
||||
sub_list->tail = sub_item;
|
||||
} else {
|
||||
sub_list->head = sub_item;
|
||||
sub_list->tail = sub_item;
|
||||
}
|
||||
sub_list->size++;
|
||||
}
|
||||
|
||||
/* Delete the entire given sub list. */
|
||||
static void
|
||||
delete_sub_list (GSubList *sub_list) {
|
||||
GSubItem *item = NULL;
|
||||
GSubItem *next = NULL;
|
||||
|
||||
if (sub_list->size == 0)
|
||||
goto clear;
|
||||
|
||||
for (item = sub_list->head; item; item = next) {
|
||||
next = item->next;
|
||||
if (item->sub_list != NULL)
|
||||
delete_sub_list (item->sub_list);
|
||||
free (item->metrics->data);
|
||||
free (item->metrics);
|
||||
free (item);
|
||||
}
|
||||
clear:
|
||||
sub_list->head = NULL;
|
||||
sub_list->size = 0;
|
||||
free (sub_list);
|
||||
}
|
||||
|
||||
/* Free malloc'd holder fields. */
|
||||
static void
|
||||
free_holder_data (GHolderItem item) {
|
||||
if (item.sub_list != NULL)
|
||||
delete_sub_list (item.sub_list);
|
||||
free_gmetrics (item.metrics);
|
||||
}
|
||||
|
||||
/* Free all memory allocated in holder for a given module. */
|
||||
void
|
||||
free_holder_by_module (GHolder **holder, GModule module) {
|
||||
uint32_t j;
|
||||
|
||||
if ((*holder) == NULL)
|
||||
return;
|
||||
|
||||
for (j = 0; j < (*holder)[module].idx; j++) {
|
||||
free_holder_data ((*holder)[module].items[j]);
|
||||
}
|
||||
free ((*holder)[module].items);
|
||||
|
||||
(*holder)[module].holder_size = 0;
|
||||
(*holder)[module].idx = 0;
|
||||
(*holder)[module].sub_items_size = 0;
|
||||
}
|
||||
|
||||
/* Free all memory allocated in holder for all modules. */
|
||||
void
|
||||
free_holder (GHolder **holder) {
|
||||
GModule module;
|
||||
uint32_t j;
|
||||
size_t idx = 0;
|
||||
|
||||
if ((*holder) == NULL)
|
||||
return;
|
||||
|
||||
FOREACH_MODULE (idx, module_list) {
|
||||
module = module_list[idx];
|
||||
|
||||
for (j = 0; j < (*holder)[module].idx; j++) {
|
||||
free_holder_data ((*holder)[module].items[j]);
|
||||
}
|
||||
free ((*holder)[module].items);
|
||||
}
|
||||
free (*holder);
|
||||
(*holder) = NULL;
|
||||
}
|
||||
|
||||
/* Iterate over holder and get the key index.
|
||||
*
|
||||
* If the key does not exist, -1 is returned.
|
||||
* On success, the key in holder is returned . */
|
||||
static int
|
||||
get_item_idx_in_holder (GHolder *holder, const char *k) {
|
||||
uint32_t i;
|
||||
if (holder == NULL)
|
||||
return KEY_NOT_FOUND;
|
||||
if (holder->idx == 0)
|
||||
return KEY_NOT_FOUND;
|
||||
if (k == NULL || *k == '\0')
|
||||
return KEY_NOT_FOUND;
|
||||
|
||||
for (i = 0; i < holder->idx; i++) {
|
||||
if (strcmp (k, holder->items[i].metrics->data) == 0)
|
||||
return i;
|
||||
}
|
||||
|
||||
return KEY_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* Sort a single GSubList in place: copy to array, sort, rebuild list.
|
||||
* Preserves each sub-item's own sub_list for recursive nesting. */
|
||||
static void
|
||||
sort_single_sub_list (GSubList **sl_ptr, GModule module, GSort sort, int max_choices_sub) {
|
||||
GHolderItem *arr;
|
||||
GSubItem *iter;
|
||||
GSubList *sub_list = *sl_ptr;
|
||||
int j, k, limit;
|
||||
|
||||
if (sub_list == NULL || sub_list->size == 0)
|
||||
return;
|
||||
|
||||
arr = new_gholder_item (sub_list->size);
|
||||
|
||||
/* copy items from the linked-list into an array, preserving sub_list */
|
||||
for (j = 0, iter = sub_list->head; iter; iter = iter->next, j++) {
|
||||
arr[j].metrics = new_gmetrics ();
|
||||
arr[j].metrics->nbw = iter->metrics->nbw;
|
||||
arr[j].metrics->data = xstrdup (iter->metrics->data);
|
||||
arr[j].metrics->hits = iter->metrics->hits;
|
||||
arr[j].metrics->id = iter->metrics->id;
|
||||
arr[j].metrics->visitors = iter->metrics->visitors;
|
||||
arr[j].sub_list = iter->sub_list;
|
||||
iter->sub_list = NULL; /* prevent double-free */
|
||||
if (conf.serve_usecs) {
|
||||
arr[j].metrics->avgts.nts = iter->metrics->avgts.nts;
|
||||
arr[j].metrics->cumts.nts = iter->metrics->cumts.nts;
|
||||
arr[j].metrics->maxts.nts = iter->metrics->maxts.nts;
|
||||
}
|
||||
}
|
||||
sort_holder_items (arr, j, sort);
|
||||
delete_sub_list (sub_list);
|
||||
|
||||
/* Only rebuild up to max_choices_sub items */
|
||||
limit = (j < max_choices_sub) ? j : max_choices_sub;
|
||||
sub_list = new_gsublist ();
|
||||
for (k = 0; k < limit; k++) {
|
||||
if (k > 0)
|
||||
sub_list = *sl_ptr;
|
||||
|
||||
add_sub_item_back (sub_list, module, arr[k].metrics);
|
||||
sub_list->tail->sub_list = arr[k].sub_list;
|
||||
*sl_ptr = sub_list;
|
||||
sub_list = NULL;
|
||||
}
|
||||
|
||||
/* Free items beyond max_choices_sub */
|
||||
for (k = limit; k < j; k++) {
|
||||
if (arr[k].sub_list != NULL)
|
||||
delete_sub_list (arr[k].sub_list);
|
||||
free (arr[k].metrics->data);
|
||||
free (arr[k].metrics);
|
||||
}
|
||||
|
||||
free (arr);
|
||||
if (sub_list) {
|
||||
delete_sub_list (sub_list);
|
||||
sub_list = NULL;
|
||||
}
|
||||
|
||||
/* recursively sort nested sub-lists */
|
||||
sub_list = *sl_ptr;
|
||||
if (sub_list != NULL) {
|
||||
for (iter = sub_list->head; iter; iter = iter->next) {
|
||||
if (iter->sub_list != NULL)
|
||||
sort_single_sub_list (&iter->sub_list, module, sort, max_choices_sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy linked-list items to an array, sort, and move them back to the
|
||||
* list. Should be faster than sorting the list */
|
||||
static void
|
||||
sort_sub_list (GHolder *h, GSort sort) {
|
||||
uint32_t i;
|
||||
|
||||
/* iterate over root-level nodes */
|
||||
for (i = 0; i < h->idx; i++) {
|
||||
sort_single_sub_list (&h->items[i].sub_list, h->module, sort, h->max_choices_sub);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the data metric field for the host panel.
|
||||
*
|
||||
* On success, the data field/metric is set. */
|
||||
static int
|
||||
set_host_child_metrics (char *data, uint8_t id, GMetrics **nmetrics) {
|
||||
GMetrics *metrics;
|
||||
|
||||
metrics = new_gmetrics ();
|
||||
metrics->data = xstrdup (data);
|
||||
metrics->id = id;
|
||||
*nmetrics = metrics;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set host panel data, including sub items.
|
||||
*
|
||||
* On success, the host panel data is set. */
|
||||
static void
|
||||
set_host_sub_list (GHolder *h, GSubList *sub_list) {
|
||||
GMetrics *nmetrics;
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
char city[CITY_LEN] = "";
|
||||
char continent[CONTINENT_LEN] = "";
|
||||
char country[ASN_LEN] = "";
|
||||
char asn[ASN_LEN] = "";
|
||||
#endif
|
||||
|
||||
char *host = h->items[h->idx].metrics->data, *hostname = NULL;
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
/* add geolocation child nodes */
|
||||
set_geolocation (host, continent, country, city, asn);
|
||||
|
||||
/* country */
|
||||
if (country[0] != '\0' && sub_list->size < h->max_choices_sub) {
|
||||
set_host_child_metrics (country, MTRC_ID_COUNTRY, &nmetrics);
|
||||
add_sub_item_back (sub_list, h->module, nmetrics);
|
||||
h->items[h->idx].sub_list = sub_list;
|
||||
h->sub_items_size++;
|
||||
|
||||
/* flag only */
|
||||
conf.has_geocountry = 1;
|
||||
}
|
||||
|
||||
/* city */
|
||||
if (city[0] != '\0' && sub_list->size < h->max_choices_sub) {
|
||||
double lat = 0.0, lng = 0.0;
|
||||
geoip_get_latlng (host, &lat, &lng);
|
||||
set_host_child_metrics (city, MTRC_ID_CITY, &nmetrics);
|
||||
nmetrics->lat = lat;
|
||||
nmetrics->lng = lng;
|
||||
add_sub_item_back (sub_list, h->module, nmetrics);
|
||||
h->items[h->idx].sub_list = sub_list;
|
||||
h->sub_items_size++;
|
||||
|
||||
/* flag only */
|
||||
conf.has_geocity = 1;
|
||||
}
|
||||
|
||||
/* ASN */
|
||||
if (asn[0] != '\0' && sub_list->size < h->max_choices_sub) {
|
||||
set_host_child_metrics (asn, MTRC_ID_ASN, &nmetrics);
|
||||
add_sub_item_back (sub_list, h->module, nmetrics);
|
||||
h->items[h->idx].sub_list = sub_list;
|
||||
h->sub_items_size++;
|
||||
|
||||
/* flag only */
|
||||
conf.has_geoasn = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* hostname */
|
||||
if (conf.enable_html_resolver && conf.output_stdout && !conf.no_ip_validation &&
|
||||
!conf.real_time_html && sub_list->size < h->max_choices_sub) {
|
||||
hostname = reverse_ip (host);
|
||||
set_host_child_metrics (hostname, MTRC_ID_HOSTNAME, &nmetrics);
|
||||
add_sub_item_back (sub_list, h->module, nmetrics);
|
||||
h->items[h->idx].sub_list = sub_list;
|
||||
h->sub_items_size++;
|
||||
free (hostname);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set host panel data, including sub items.
|
||||
*
|
||||
* On success, the host panel data is set. */
|
||||
static void
|
||||
add_host_child_to_holder (GHolder *h) {
|
||||
GMetrics *nmetrics;
|
||||
GSubList *sub_list = new_gsublist ();
|
||||
|
||||
char *ip = h->items[h->idx].metrics->data;
|
||||
char *hostname = NULL;
|
||||
uint32_t n = h->sub_items_size;
|
||||
|
||||
/* add child nodes */
|
||||
set_host_sub_list (h, sub_list);
|
||||
|
||||
pthread_mutex_lock (&gdns_thread.mutex);
|
||||
hostname = ht_get_hostname (ip);
|
||||
pthread_mutex_unlock (&gdns_thread.mutex);
|
||||
|
||||
/* determine if we have the IP's hostname */
|
||||
if (!hostname) {
|
||||
dns_resolver (ip);
|
||||
} else if (hostname && sub_list->size < h->max_choices_sub) {
|
||||
set_host_child_metrics (hostname, MTRC_ID_HOSTNAME, &nmetrics);
|
||||
add_sub_item_back (sub_list, h->module, nmetrics);
|
||||
h->items[h->idx].sub_list = sub_list;
|
||||
h->sub_items_size++;
|
||||
free (hostname);
|
||||
} else if (hostname) {
|
||||
free (hostname);
|
||||
}
|
||||
|
||||
/* did not add any items */
|
||||
if (n == h->sub_items_size)
|
||||
free (sub_list);
|
||||
}
|
||||
|
||||
/* Given a GRawDataType, set the data and hits value.
|
||||
*
|
||||
* On error, no values are set and 1 is returned.
|
||||
* On success, the data and hits values are set and 0 is returned. */
|
||||
static int
|
||||
map_data (GModule module, GRawDataItem item, datatype type, char **data, uint32_t *hits) {
|
||||
switch (type) {
|
||||
case U32:
|
||||
if (!(*data = ht_get_datamap (module, item.nkey)))
|
||||
return 1;
|
||||
if (!item.hits)
|
||||
return 1;
|
||||
*hits = item.hits;
|
||||
break;
|
||||
case STR:
|
||||
if (!(*hits = ht_get_hits (module, item.nkey)))
|
||||
return 1;
|
||||
*data = xstrdup (item.data);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Given a data item, store it into a holder structure. */
|
||||
static void
|
||||
set_single_metrics (GRawDataItem item, GHolder *h, char *data, uint32_t hits) {
|
||||
uint32_t visitors = 0;
|
||||
uint64_t bw = 0, cumts = 0, maxts = 0;
|
||||
|
||||
bw = ht_get_bw (h->module, item.nkey);
|
||||
cumts = ht_get_cumts (h->module, item.nkey);
|
||||
maxts = ht_get_maxts (h->module, item.nkey);
|
||||
visitors = ht_get_visitors (h->module, item.nkey);
|
||||
|
||||
h->items[h->idx].metrics = new_gmetrics ();
|
||||
h->items[h->idx].metrics->hits = hits;
|
||||
h->items[h->idx].metrics->data = data;
|
||||
h->items[h->idx].metrics->visitors = visitors;
|
||||
h->items[h->idx].metrics->nbw = bw;
|
||||
h->items[h->idx].metrics->avgts.nts = cumts / hits;
|
||||
h->items[h->idx].metrics->cumts.nts = cumts;
|
||||
h->items[h->idx].metrics->maxts.nts = maxts;
|
||||
|
||||
if (bw && !conf.bandwidth)
|
||||
conf.bandwidth = 1;
|
||||
if (cumts && !conf.serve_usecs)
|
||||
conf.serve_usecs = 1;
|
||||
|
||||
if (conf.append_method) {
|
||||
h->items[h->idx].metrics->method = ht_get_method (h->module, item.nkey);
|
||||
}
|
||||
|
||||
if (conf.append_protocol) {
|
||||
h->items[h->idx].metrics->protocol = ht_get_protocol (h->module, item.nkey);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set all panel data. This will set data for panels that do not
|
||||
* contain sub items. A function pointer is used for post data set. */
|
||||
static void
|
||||
add_data_to_holder (GRawDataItem item, GHolder *h, datatype type, const GPanel *panel) {
|
||||
char *data = NULL;
|
||||
uint32_t hits = 0;
|
||||
|
||||
if (map_data (h->module, item, type, &data, &hits) == 1)
|
||||
return;
|
||||
|
||||
set_single_metrics (item, h, data, hits);
|
||||
if (panel->holder_callback)
|
||||
panel->holder_callback (h);
|
||||
|
||||
h->idx++;
|
||||
}
|
||||
|
||||
/* A wrapper to set a host item */
|
||||
static void
|
||||
set_host (GRawDataItem item, GHolder *h, const GPanel *panel, char *data, uint32_t hits) {
|
||||
set_single_metrics (item, h, xstrdup (data), hits);
|
||||
if (panel->holder_callback)
|
||||
panel->holder_callback (h);
|
||||
h->idx++;
|
||||
}
|
||||
|
||||
/* Set all panel data. This will set data for panels that do not
|
||||
* contain sub items. A function pointer is used for post data set. */
|
||||
static void
|
||||
add_host_to_holder (GRawDataItem item, GHolder *h, datatype type, const GPanel *panel) {
|
||||
char buf4[INET_ADDRSTRLEN];
|
||||
char buf6[INET6_ADDRSTRLEN];
|
||||
char *data = NULL;
|
||||
uint32_t hits = 0;
|
||||
unsigned i;
|
||||
|
||||
struct in6_addr addr6, mask6, nwork6;
|
||||
struct in_addr addr4, mask4, nwork4;
|
||||
|
||||
const char *arr4[] = { "255.255.255.0", "255.255.0.0", "255.0.0.0" };
|
||||
const char *arr6[] = {
|
||||
"ffff:ffff:ffff:ffff:0000:0000:0000:0000",
|
||||
"ffff:ffff:ffff:0000:0000:0000:0000:0000",
|
||||
"ffff:ffff:0000:0000:0000:0000:0000:0000"
|
||||
};
|
||||
|
||||
const char *m4 = arr4[0];
|
||||
const char *m6 = arr6[0];
|
||||
|
||||
if (map_data (h->module, item, type, &data, &hits) == 1)
|
||||
return;
|
||||
|
||||
if (!conf.anonymize_ip) {
|
||||
add_data_to_holder (item, h, type, panel);
|
||||
free (data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (conf.anonymize_level == ANONYMIZE_STRONG || conf.anonymize_level == ANONYMIZE_PEDANTIC) {
|
||||
m4 = arr4[conf.anonymize_level - 1];
|
||||
m6 = arr6[conf.anonymize_level - 1];
|
||||
}
|
||||
|
||||
if (1 == inet_pton (AF_INET, data, &addr4)) {
|
||||
if (1 == inet_pton (AF_INET, m4, &mask4)) {
|
||||
memset (buf4, 0, sizeof *buf4);
|
||||
nwork4.s_addr = addr4.s_addr & mask4.s_addr;
|
||||
|
||||
if (inet_ntop (AF_INET, &nwork4, buf4, INET_ADDRSTRLEN) != NULL) {
|
||||
set_host (item, h, panel, buf4, hits);
|
||||
free (data);
|
||||
}
|
||||
}
|
||||
} else if (1 == inet_pton (AF_INET6, data, &addr6)) {
|
||||
if (1 == inet_pton (AF_INET6, m6, &mask6)) {
|
||||
memset (buf6, 0, sizeof *buf6);
|
||||
for (i = 0; i < 16; i++) {
|
||||
nwork6.s6_addr[i] = addr6.s6_addr[i] & mask6.s6_addr[i];
|
||||
}
|
||||
|
||||
if (inet_ntop (AF_INET6, &nwork6, buf6, INET6_ADDRSTRLEN) != NULL) {
|
||||
set_host (item, h, panel, buf6, hits);
|
||||
free (data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set all root panel data. This will set the root nodes. */
|
||||
static int
|
||||
set_root_metrics (GRawDataItem item, GModule module, datatype type, GMetrics **nmetrics) {
|
||||
GMetrics *metrics;
|
||||
char *data = NULL;
|
||||
uint64_t bw = 0, cumts = 0, maxts = 0;
|
||||
uint32_t hits = 0, visitors = 0;
|
||||
|
||||
if (map_data (module, item, type, &data, &hits) == 1)
|
||||
return 1;
|
||||
|
||||
bw = ht_get_bw (module, item.nkey);
|
||||
cumts = ht_get_cumts (module, item.nkey);
|
||||
maxts = ht_get_maxts (module, item.nkey);
|
||||
visitors = ht_get_visitors (module, item.nkey);
|
||||
|
||||
metrics = new_gmetrics ();
|
||||
metrics->avgts.nts = cumts / hits;
|
||||
metrics->cumts.nts = cumts;
|
||||
metrics->maxts.nts = maxts;
|
||||
metrics->nbw = bw;
|
||||
metrics->data = data;
|
||||
metrics->hits = hits;
|
||||
metrics->visitors = visitors;
|
||||
*nmetrics = metrics;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set all root panel data, including sub list items. */
|
||||
static void
|
||||
add_root_to_holder (GRawDataItem item, GHolder *h, datatype type, GO_UNUSED const GPanel *panel) {
|
||||
GSubList *sub_list;
|
||||
GMetrics *metrics, *nmetrics;
|
||||
char *root = NULL;
|
||||
int root_idx = KEY_NOT_FOUND, idx = 0;
|
||||
|
||||
if (set_root_metrics (item, h->module, type, &nmetrics) == 1)
|
||||
return;
|
||||
|
||||
if (!(root = ht_get_root (h->module, item.nkey))) {
|
||||
free_gmetrics (nmetrics);
|
||||
return;
|
||||
}
|
||||
|
||||
/* add data as a child node into holder */
|
||||
if (KEY_NOT_FOUND == (root_idx = get_item_idx_in_holder (h, root))) {
|
||||
/* Check if we've reached max_choices for root items */
|
||||
if (h->idx >= h->max_choices) {
|
||||
free (root);
|
||||
free_gmetrics (nmetrics);
|
||||
return;
|
||||
}
|
||||
idx = h->idx;
|
||||
sub_list = new_gsublist ();
|
||||
metrics = new_gmetrics ();
|
||||
|
||||
h->items[idx].metrics = metrics;
|
||||
h->items[idx].metrics->data = root;
|
||||
h->idx++;
|
||||
} else {
|
||||
sub_list = h->items[root_idx].sub_list;
|
||||
metrics = h->items[root_idx].metrics;
|
||||
|
||||
idx = root_idx;
|
||||
free (root);
|
||||
}
|
||||
|
||||
/* Accumulate metrics into parent regardless of whether we add the sub-item */
|
||||
h->items[idx].metrics = metrics;
|
||||
h->items[idx].metrics->cumts.nts += nmetrics->cumts.nts;
|
||||
h->items[idx].metrics->nbw += nmetrics->nbw;
|
||||
h->items[idx].metrics->hits += nmetrics->hits;
|
||||
h->items[idx].metrics->visitors += nmetrics->visitors;
|
||||
h->items[idx].metrics->avgts.nts = h->items[idx].metrics->cumts.nts / h->items[idx].metrics->hits;
|
||||
|
||||
if (nmetrics->maxts.nts > h->items[idx].metrics->maxts.nts)
|
||||
h->items[idx].metrics->maxts.nts = nmetrics->maxts.nts;
|
||||
|
||||
/* Only add sub-item if we haven't reached max_choices_sub for this sub-list */
|
||||
if (sub_list->size < h->max_choices_sub) {
|
||||
add_sub_item_back (sub_list, h->module, nmetrics);
|
||||
h->items[idx].sub_list = sub_list;
|
||||
h->sub_items_size++;
|
||||
} else {
|
||||
free_gmetrics (nmetrics);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
/* Find a sub-item in a GSubList by its data string.
|
||||
* Returns the matching GSubItem, or NULL if not found. */
|
||||
static GSubItem *
|
||||
find_sub_item_by_data (GSubList *sl, const char *data) {
|
||||
GSubItem *iter;
|
||||
if (sl == NULL || data == NULL)
|
||||
return NULL;
|
||||
for (iter = sl->head; iter; iter = iter->next) {
|
||||
if (strcmp (iter->metrics->data, data) == 0)
|
||||
return iter;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Build 3-level GEO_LOCATION hierarchy: Continent > Country > City.
|
||||
* Falls back to add_root_to_holder (2-level) when has_geocity is false. */
|
||||
static void
|
||||
add_geo_to_holder (GRawDataItem item, GHolder *h, datatype type, GO_UNUSED const GPanel *panel) {
|
||||
GMetrics *nmetrics;
|
||||
GSubList *sub_list;
|
||||
GSubItem *country_sub;
|
||||
GMetrics *cont_metrics, *country_metrics;
|
||||
char *root = NULL;
|
||||
const char *continent = NULL;
|
||||
int root_idx = KEY_NOT_FOUND, idx = 0;
|
||||
|
||||
if (!conf.has_geocity) {
|
||||
add_root_to_holder (item, h, type, panel);
|
||||
return;
|
||||
}
|
||||
|
||||
/* city metrics from storage (city is the "data" key) */
|
||||
if (set_root_metrics (item, h->module, type, &nmetrics) == 1)
|
||||
return;
|
||||
|
||||
/* country is the "root" of the city in storage */
|
||||
if (!(root = ht_get_root (h->module, item.nkey))) {
|
||||
free_gmetrics (nmetrics);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Extract continent from root if encoded as "continent|country".
|
||||
* Falls back to country_continent_map for old persisted data. */
|
||||
{
|
||||
char *sep = strchr (root, 0x7c);
|
||||
if (sep) {
|
||||
*sep = 0x00;
|
||||
continent = xstrdup (root); /* copy before freeing root */
|
||||
char *new_root = xstrdup (sep + 1);
|
||||
free (root);
|
||||
root = new_root;
|
||||
}
|
||||
}
|
||||
if (!continent)
|
||||
continent = get_continent_for_country (root);
|
||||
if (continent == NULL) {
|
||||
/* fallback: use country as root directly (2-level) */
|
||||
free (root);
|
||||
add_root_to_holder (item, h, type, panel);
|
||||
free_gmetrics (nmetrics);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Find or create continent as root GHolderItem */
|
||||
if (KEY_NOT_FOUND == (root_idx = get_item_idx_in_holder (h, continent))) {
|
||||
/* Check if we've reached max_choices for root items (continents) */
|
||||
if (h->idx >= h->max_choices) {
|
||||
free (root);
|
||||
free_gmetrics (nmetrics);
|
||||
return;
|
||||
}
|
||||
idx = h->idx;
|
||||
cont_metrics = new_gmetrics ();
|
||||
cont_metrics->data = xstrdup (continent);
|
||||
h->items[idx].metrics = cont_metrics;
|
||||
h->items[idx].sub_list = new_gsublist ();
|
||||
h->idx++;
|
||||
} else {
|
||||
idx = root_idx;
|
||||
cont_metrics = h->items[idx].metrics;
|
||||
}
|
||||
|
||||
/* Find or create country as sub-item under continent */
|
||||
sub_list = h->items[idx].sub_list;
|
||||
country_sub = find_sub_item_by_data (sub_list, root);
|
||||
if (country_sub == NULL) {
|
||||
/* Only add country if continent's sub-list hasn't reached max_choices_sub */
|
||||
if (sub_list->size < h->max_choices_sub) {
|
||||
country_metrics = new_gmetrics ();
|
||||
country_metrics->data = xstrdup (root);
|
||||
add_sub_item_back (sub_list, h->module, country_metrics);
|
||||
country_sub = sub_list->tail;
|
||||
country_sub->sub_list = new_gsublist ();
|
||||
h->sub_items_size++;
|
||||
} else {
|
||||
/* Continent sub-list is full, accumulate to continent and skip country/city */
|
||||
cont_metrics->hits += nmetrics->hits;
|
||||
cont_metrics->visitors += nmetrics->visitors;
|
||||
cont_metrics->nbw += nmetrics->nbw;
|
||||
cont_metrics->cumts.nts += nmetrics->cumts.nts;
|
||||
if (nmetrics->maxts.nts > cont_metrics->maxts.nts)
|
||||
cont_metrics->maxts.nts = nmetrics->maxts.nts;
|
||||
if (cont_metrics->hits > 0)
|
||||
cont_metrics->avgts.nts = cont_metrics->cumts.nts / cont_metrics->hits;
|
||||
free_gmetrics (nmetrics);
|
||||
free (root);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
country_metrics = country_sub->metrics;
|
||||
/* Handle mixed 2-level (persisted) and 3-level (live) data:
|
||||
* older entries won't have a city sub-list. */
|
||||
if (country_sub->sub_list == NULL)
|
||||
country_sub->sub_list = new_gsublist ();
|
||||
}
|
||||
|
||||
/* Accumulate metrics upward: city -> country -> continent */
|
||||
country_metrics->hits += nmetrics->hits;
|
||||
country_metrics->visitors += nmetrics->visitors;
|
||||
country_metrics->nbw += nmetrics->nbw;
|
||||
country_metrics->cumts.nts += nmetrics->cumts.nts;
|
||||
if (nmetrics->maxts.nts > country_metrics->maxts.nts)
|
||||
country_metrics->maxts.nts = nmetrics->maxts.nts;
|
||||
if (country_metrics->hits > 0)
|
||||
country_metrics->avgts.nts = country_metrics->cumts.nts / country_metrics->hits;
|
||||
|
||||
cont_metrics->hits += nmetrics->hits;
|
||||
cont_metrics->visitors += nmetrics->visitors;
|
||||
cont_metrics->nbw += nmetrics->nbw;
|
||||
cont_metrics->cumts.nts += nmetrics->cumts.nts;
|
||||
if (nmetrics->maxts.nts > cont_metrics->maxts.nts)
|
||||
cont_metrics->maxts.nts = nmetrics->maxts.nts;
|
||||
if (cont_metrics->hits > 0)
|
||||
cont_metrics->avgts.nts = cont_metrics->cumts.nts / cont_metrics->hits;
|
||||
|
||||
/* Add city as sub-item under country (only if country's sub-list hasn't reached max_choices_sub) */
|
||||
if (country_sub->sub_list->size < h->max_choices_sub) {
|
||||
add_sub_item_back (country_sub->sub_list, h->module, nmetrics);
|
||||
h->sub_items_size++;
|
||||
} else {
|
||||
free_gmetrics (nmetrics);
|
||||
}
|
||||
|
||||
free (root);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Load raw data into our holder structure */
|
||||
void
|
||||
load_holder_data (GRawData *raw_data, GHolder *h, GModule module, GSort sort, uint32_t max_choices, uint32_t max_choices_sub) {
|
||||
uint32_t i;
|
||||
uint32_t size = 0;
|
||||
uint32_t alloc_size = 0;
|
||||
const GPanel *panel = panel_lookup (module);
|
||||
/* Hierarchical panels group multiple raw items under fewer root items */
|
||||
int is_hierarchical = (panel->insert == add_root_to_holder ||
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
panel->insert == add_geo_to_holder ||
|
||||
#endif
|
||||
0);
|
||||
|
||||
#ifdef _DEBUG
|
||||
clock_t begin = clock ();
|
||||
double taken;
|
||||
const char *modstr = NULL;
|
||||
LOG_DEBUG (("== load_holder_data ==\n"));
|
||||
#endif
|
||||
|
||||
size = raw_data->size;
|
||||
/* For hierarchical data, we don't know how many root items we'll create,
|
||||
* but it can't be more than size or max_choices */
|
||||
alloc_size = size > max_choices ? max_choices : size;
|
||||
h->holder_size = is_hierarchical ? size : (size > max_choices ? max_choices : size);
|
||||
h->ht_size = size;
|
||||
h->idx = 0;
|
||||
h->module = module;
|
||||
h->sub_items_size = 0;
|
||||
h->max_choices = max_choices;
|
||||
h->max_choices_sub = max_choices_sub;
|
||||
h->items = new_gholder_item (alloc_size);
|
||||
|
||||
for (i = 0; i < h->holder_size; i++) {
|
||||
panel->insert (raw_data->items[i], h, raw_data->type, panel);
|
||||
}
|
||||
sort_holder_items (h->items, h->idx, sort);
|
||||
if (h->sub_items_size)
|
||||
sort_sub_list (h, sort);
|
||||
free_raw_data (raw_data);
|
||||
|
||||
#ifdef _DEBUG
|
||||
modstr = get_module_str (module);
|
||||
taken = (double) (clock () - begin) / CLOCKS_PER_SEC;
|
||||
LOG_DEBUG (("== %-30s%f\n\n", modstr, taken));
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GHOLDER_H_INCLUDED
|
||||
#define GHOLDER_H_INCLUDED
|
||||
|
||||
#define MTRC_ID_COUNTRY 0
|
||||
#define MTRC_ID_CITY 1
|
||||
#define MTRC_ID_ASN 2
|
||||
#define MTRC_ID_HOSTNAME 3
|
||||
|
||||
#include "commons.h"
|
||||
#include "sort.h"
|
||||
|
||||
/* Default Anonymization Levels */
|
||||
typedef enum GAnonymizeLevels_ {
|
||||
ANONYMIZE_DEFAULT = 1,
|
||||
ANONYMIZE_STRONG,
|
||||
ANONYMIZE_PEDANTIC,
|
||||
} GAnonymizeLevels;
|
||||
|
||||
/* Function Prototypes */
|
||||
GHolder *new_gholder (uint32_t size);
|
||||
void *add_hostname_node (void *ptr_holder);
|
||||
void free_holder_by_module (GHolder ** holder, GModule module);
|
||||
void free_holder (GHolder ** holder);
|
||||
void load_holder_data (GRawData * raw_data, GHolder * h, GModule module, GSort sort, uint32_t max_choices, uint32_t max_choices_sub);
|
||||
void load_host_to_holder (GHolder * h, char *ip);
|
||||
int dup_key_list (void *val, GSLList ** user_data);
|
||||
|
||||
#endif // for #ifndef GHOLDER_H
|
||||
+1653
File diff suppressed because it is too large
Load Diff
+196
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GKHASH_H_INCLUDED
|
||||
#define GKHASH_H_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "gslist.h"
|
||||
#include "gstorage.h"
|
||||
#include "khash.h"
|
||||
#include "parser.h"
|
||||
|
||||
#include "gkmhash.h"
|
||||
|
||||
#define DB_VERSION 2
|
||||
#define DB_INSTANCE 1
|
||||
|
||||
typedef struct GKDB_ GKDB;
|
||||
|
||||
typedef struct GKHashStorage_ GKHashStorage;
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
/* uint32_t keys , GKDB payload */
|
||||
KHASH_MAP_INIT_INT (igdb , GKDB *);
|
||||
/* uint32_t keys , GKHashStorage payload */
|
||||
KHASH_MAP_INIT_INT (igkh , GKHashStorage *);
|
||||
/* uint32_t keys , uint32_t payload */
|
||||
KHASH_MAP_INIT_INT (ii32 , uint32_t);
|
||||
/* uint32_t keys , string payload */
|
||||
KHASH_MAP_INIT_INT (is32 , char *);
|
||||
/* uint32_t keys , uint64_t payload */
|
||||
KHASH_MAP_INIT_INT (iu64 , uint64_t);
|
||||
/* string keys , uint32_t payload */
|
||||
KHASH_MAP_INIT_STR (si32 , uint32_t);
|
||||
/* string keys , uint8_t payload */
|
||||
KHASH_MAP_INIT_STR (si08 , uint8_t);
|
||||
/* uint8_t keys , uint8_t payload */
|
||||
KHASH_MAP_INIT_INT (ii08 , uint8_t);
|
||||
/* string keys , string payload */
|
||||
KHASH_MAP_INIT_STR (ss32 , char *);
|
||||
/* uint64_t key , GLastParse payload */
|
||||
KHASH_MAP_INIT_INT64 (iglp , GLastParse);
|
||||
/* uint32_t keys , GSLList payload */
|
||||
KHASH_MAP_INIT_INT (igsl , GSLList *);
|
||||
/* string keys , uint64_t payload */
|
||||
KHASH_MAP_INIT_STR (su64 , uint64_t);
|
||||
/* uint64_t key , uint8_t payload */
|
||||
KHASH_MAP_INIT_INT64 (u648 , uint8_t);
|
||||
/* *INDENT-ON* */
|
||||
|
||||
/* Whole App Data store */
|
||||
typedef struct GKHashDB_ {
|
||||
GKHashMetric metrics[GAMTRC_TOTAL];
|
||||
} GKHashDB;
|
||||
|
||||
/* DB */
|
||||
struct GKDB_ {
|
||||
GKHashDB *hdb; /* app-level hash tables */
|
||||
Logs *logs; /* logs parsing per db instance */
|
||||
GKHashModule *cache; /* cache modules */
|
||||
GKHashStorage *store; /* per date OR module */
|
||||
};
|
||||
|
||||
#define HT_FIRST_VAL(h, kvar, code) { khint_t __k; \
|
||||
for (__k = kh_begin(h); __k != kh_end(h); ++__k) { \
|
||||
if (!kh_exist(h,__k)) continue; \
|
||||
(kvar) = kh_key(h,__k); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
#define HT_SUM_VAL(h, kvar, code) { khint_t __k; \
|
||||
for (__k = kh_begin(h); __k != kh_end(h); ++__k) { \
|
||||
if (!kh_exist(h,__k)) continue; \
|
||||
(kvar) = kh_key(h,__k); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
#define HT_FOREACH_KEY(h, kvar, code) { khint_t __k; \
|
||||
for (__k = kh_begin(h); __k != kh_end(h); ++__k) { \
|
||||
if (!kh_exist(h,__k)) continue; \
|
||||
(kvar) = kh_key(h,__k); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
extern const GKHashMetric app_metrics[];
|
||||
extern const size_t app_metrics_len;
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
void *new_igsl_ht (void);
|
||||
void *new_ii08_ht (void);
|
||||
void *new_ii32_ht (void);
|
||||
void *new_is32_ht (void);
|
||||
void *new_iu64_ht (void);
|
||||
void *new_si32_ht (void);
|
||||
void *new_su64_ht (void);
|
||||
void *new_u648_ht (void);
|
||||
|
||||
void del_igsl_free (void *h, uint8_t free_data);
|
||||
void del_ii08 (void *h, GO_UNUSED uint8_t free_data);
|
||||
void del_ii32 (void *h, GO_UNUSED uint8_t free_data);
|
||||
void del_is32_free (void *h, uint8_t free_data);
|
||||
void del_iu64 (void *h, GO_UNUSED uint8_t free_data);
|
||||
void del_si32_free (void *h, uint8_t free_data);
|
||||
void del_su64_free (void *h, uint8_t free_data);
|
||||
void del_u648 (void *h, GO_UNUSED uint8_t free_data);
|
||||
void des_igsl_free (void *h, uint8_t free_data);
|
||||
void des_ii08 (void *h, GO_UNUSED uint8_t free_data);
|
||||
void des_ii32 (void *h, GO_UNUSED uint8_t free_data);
|
||||
void des_is32_free (void *h, uint8_t free_data);
|
||||
void des_iu64 (void *h, GO_UNUSED uint8_t free_data);
|
||||
void des_si32_free (void *h, uint8_t free_data);
|
||||
void des_su64_free (void *h, uint8_t free_data);
|
||||
void des_u648 (void *h, GO_UNUSED uint8_t free_data);
|
||||
|
||||
int inc_iu64 (khash_t (iu64) * hash, uint32_t key, uint64_t inc);
|
||||
int inc_su64 (khash_t (su64) * hash, const char *key, uint64_t inc);
|
||||
int ins_iglp (khash_t (iglp) * hash, uint64_t key, const GLastParse *lp);
|
||||
int ins_igsl (khash_t (igsl) * hash, uint32_t key, uint32_t value);
|
||||
int ins_ii08 (khash_t (ii08) * hash, uint32_t key, uint8_t value);
|
||||
int ins_ii32 (khash_t (ii32) * hash, uint32_t key, uint32_t value);
|
||||
int ins_is32 (khash_t (is32) * hash, uint32_t key, char *value);
|
||||
int ins_iu64 (khash_t (iu64) * hash, uint32_t key, uint64_t value);
|
||||
int ins_si08 (khash_t (si08) * hash, const char *key, uint8_t value);
|
||||
int ins_si32 (khash_t (si32) * hash, const char *key, uint32_t value);
|
||||
int ins_su64 (khash_t (su64) * hash, const char *key, uint64_t value);
|
||||
int ins_u648 (khash_t (u648) * hash, uint64_t key, uint8_t value);
|
||||
uint32_t inc_ii32 (khash_t (ii32) * hash, uint32_t key, uint32_t inc);
|
||||
uint32_t ins_ii32_ai (khash_t (ii32) * hash, uint32_t key);
|
||||
uint32_t ins_ii32_inc (khash_t (ii32) * hash, uint32_t key, uint32_t (*cb) (khash_t (si32) *, const char *), khash_t (si32) * seqs, const char *seqk);
|
||||
uint32_t ins_si32_inc (khash_t (si32) * hash, const char *key, uint32_t (*cb) (khash_t (si32) *, const char *), khash_t (si32) * seqs, const char *seqk);
|
||||
|
||||
char *get_is32 (khash_t (is32) * hash, uint32_t key);
|
||||
uint32_t get_ii32 (khash_t (ii32) * hash, uint32_t key);
|
||||
uint32_t get_si32 (khash_t (si32) * hash, const char *key);
|
||||
uint64_t get_iu64 (khash_t (iu64) * hash, uint32_t key);
|
||||
uint64_t get_su64 (khash_t (su64) * hash, const char *key);
|
||||
uint8_t get_ii08 (khash_t (ii08) * hash, uint32_t key);
|
||||
uint8_t get_si08 (khash_t (si08) * hash, const char *key);
|
||||
void get_ii32_min_max (khash_t (ii32) * hash, uint32_t * min, uint32_t * max);
|
||||
void get_iu64_min_max (khash_t (iu64) * hash, uint64_t * min, uint64_t * max);
|
||||
|
||||
int ht_insert_hostname (const char *ip, const char *host);
|
||||
int ht_insert_json_logfmt (GO_UNUSED void *userdata, char *key, char *spec);
|
||||
int ht_insert_last_parse (uint64_t key, const GLastParse *lp);
|
||||
uint32_t ht_inc_cnt_overall (const char *key, uint32_t val);
|
||||
uint32_t ht_ins_seq (khash_t (si32) * hash, const char *key);
|
||||
uint8_t ht_insert_meth_proto (const char *key);
|
||||
|
||||
char *ht_get_hostname (const char *host);
|
||||
char *ht_get_json_logfmt (const char *key);
|
||||
uint32_t ht_get_excluded_ips (void);
|
||||
uint32_t ht_get_invalid (void);
|
||||
uint32_t ht_get_processed (void);
|
||||
uint32_t ht_get_processing_time (void);
|
||||
uint8_t get_method_proto (const char *value);
|
||||
uint32_t *get_sorted_dates (uint32_t * len);
|
||||
|
||||
void free_storage (void);
|
||||
void init_pre_storage (Logs *logs);
|
||||
|
||||
const char *get_mtr_type_str (GSMetricType type);
|
||||
void *get_db_instance (uint32_t key);
|
||||
void *get_hdb (GKDB * db, GAMetric mtrc);
|
||||
|
||||
GLastParse ht_get_last_parse (uint64_t key);
|
||||
Logs *get_db_logs(uint32_t instance);
|
||||
/* *INDENT-ON* */
|
||||
|
||||
#endif // for #ifndef GKHASH_H
|
||||
+1465
File diff suppressed because it is too large
Load Diff
+275
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GKMHASH_H_INCLUDED
|
||||
#define GKMHASH_H_INCLUDED
|
||||
|
||||
#include "gstorage.h"
|
||||
|
||||
typedef struct GKHashMetric_ GKHashMetric;
|
||||
|
||||
/* Data store per module */
|
||||
typedef struct GKHashModule_ {
|
||||
GModule module;
|
||||
GKHashMetric metrics[GSMTRC_TOTAL];
|
||||
} GKHashModule;
|
||||
|
||||
/* Data store global */
|
||||
typedef struct GKHashGlobal_ {
|
||||
GKHashMetric metrics[GSMTRC_TOTAL];
|
||||
} GKHashGlobal;
|
||||
|
||||
struct GKHashStorage_ {
|
||||
GKHashModule *mhash; /* modules */
|
||||
GKHashGlobal *ghash; /* global */
|
||||
};
|
||||
|
||||
/* Metrics Storage */
|
||||
|
||||
/* Most metrics are encapsulated within a GKHashStorage structure, which is
|
||||
* conformed of a dated key and a GKHashStorage struct value. This helps to
|
||||
* easily destroy the entire dated storage at any time. */
|
||||
|
||||
/* GLOBAL METRICS */
|
||||
/* ============== */
|
||||
/* Maps a string key containing an IP|DATE|UA(hash uint32_t => hex) to an
|
||||
* autoincremented value.
|
||||
*
|
||||
* 192.168.0.1|27/Apr/2020|7E8E0E -> 1
|
||||
* 192.168.0.1|28/Apr/2020|7E8E0E -> 2
|
||||
*/
|
||||
/*khash_t(si32) MTRC_UNIQUE_KEYS */
|
||||
|
||||
/* Maps string keys made out of the user agent to an autoincremented value.
|
||||
*
|
||||
* Debian APT-HTTP/1.3 (1.0.9.8.5) -> 1838302 -> 1
|
||||
* Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1;) -> 8723842 -> 2
|
||||
*/
|
||||
/*khash_t(ii32) MTRC_AGENT_KEYS */
|
||||
|
||||
/* Maps integer keys from the autoincremented MTRC_AGENT_KEYS value to the user
|
||||
* agent.
|
||||
*
|
||||
* 1 -> Debian APT-HTTP/1.3 (1.0.9.8.5)
|
||||
* 2 -> Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)
|
||||
*/
|
||||
/*khash_t(is32) MTRC_AGENT_VALS */
|
||||
|
||||
/* Maps a single numeric key (usually 1) to an autoincremented hits value.
|
||||
*
|
||||
* 1 -> 5
|
||||
*/
|
||||
/*khash_t(is32) MTRC_CNT_VALID */
|
||||
|
||||
/* Maps a single numeric key (usually 1) to an autoincremented bw value.
|
||||
*
|
||||
* 1 -> 592933
|
||||
*/
|
||||
/*khash_t(iu64) MTRC_CNT_BW */
|
||||
|
||||
|
||||
/* MODULE METRICS */
|
||||
/* ============== */
|
||||
/* Maps keys (string) to a hash to a numeric values (uint32_t).
|
||||
* this mitigates the issue of having multiple stores
|
||||
* with the same string key, and therefore, avoids unnecessary
|
||||
* memory usage (in most cases).
|
||||
*
|
||||
* HEAD|/index.php -> 9872347 -> 1
|
||||
* POST|/index.php -> 3452345 -> 2
|
||||
* Windows XP -> 2842343 -> 3
|
||||
* Ubuntu 10.10 -> 1852342 -> 4
|
||||
* GET|Ubuntu 10.10-> 4872343 -> 5
|
||||
* GNU+Linux -> 5862347 -> 6
|
||||
* 26/Dec/2014 -> 9874347 -> 7
|
||||
* Windows -> 3875347 -> 8
|
||||
*/
|
||||
/*khash_t(si32) MTRC_KEYMAP */
|
||||
|
||||
/* Maps integer keys of root elements from the keymap hash
|
||||
* to actual string values.
|
||||
*
|
||||
* 6 -> GNU+Linux
|
||||
* 8 -> Windows
|
||||
*/
|
||||
/*khash_t(is32) MTRC_ROOTMAP */
|
||||
|
||||
/* Maps integer keys of data elements from the keymap hash
|
||||
* to actual string values.
|
||||
*
|
||||
* 1 -> /index.php
|
||||
* 2 -> /index.php
|
||||
* 3 -> Windows xp
|
||||
* 4 -> Ubuntu 10.10
|
||||
* 5 -> Ubuntu 10.10
|
||||
* 7 -> 26/dec/2014
|
||||
*/
|
||||
/*khash_t(is32) MTRC_DATAMAP */
|
||||
|
||||
/* Maps the unique uint32_t key of the IP/date/UA and the uint32_t key from the
|
||||
* data field encoded into a uint64_t key to numeric autoincremented values.
|
||||
* e.g., "1&4" => 12938238293 to ai.
|
||||
*
|
||||
* 201023232 -> 1
|
||||
* 202939232 -> 2
|
||||
*/
|
||||
/*khash_t(si32) MTRC_UNIQMAP */
|
||||
|
||||
/* Maps integer keys made from a data key to an integer root key in
|
||||
* MTRC_KEYMAP.
|
||||
*
|
||||
* 4 -> 6
|
||||
* 3 -> 8
|
||||
*/
|
||||
/*khash_t(ii32) MTRC_ROOT */
|
||||
|
||||
/* Maps integer key from the keymap hash to the number of
|
||||
* hits.
|
||||
*
|
||||
* 1 -> 10934
|
||||
* 2 -> 3231
|
||||
* 3 -> 500
|
||||
* 4 -> 201
|
||||
* 5 -> 206
|
||||
*/
|
||||
/*khash_t(ii32) MTRC_HITS */
|
||||
|
||||
/* Maps numeric keys made from the uniqmap store to autoincremented values
|
||||
* (counter).
|
||||
* 10 -> 100
|
||||
* 40 -> 56
|
||||
*/
|
||||
/*khash_t(ii32) MTRC_VISITORS */
|
||||
|
||||
/* Maps numeric data keys to bandwidth (in bytes).
|
||||
* 1 -> 1024
|
||||
* 2 -> 2048
|
||||
*/
|
||||
/*khash_t(iu64) MTRC_BW */
|
||||
|
||||
/* Maps numeric data keys to cumulative time served (in usecs/msecs).
|
||||
* 1 -> 187
|
||||
* 2 -> 208
|
||||
*/
|
||||
/*khash_t(iu64) MTRC_CUMTS */
|
||||
|
||||
/* Maps numeric data keys to max time served (in usecs/msecs).
|
||||
* 1 -> 1287
|
||||
* 2 -> 2308
|
||||
*/
|
||||
/*khash_t(iu64) MTRC_MAXTS */
|
||||
|
||||
/* Maps numeric data keys to uint8_t values.
|
||||
* 1 -> 3
|
||||
* 2 -> 4
|
||||
*/
|
||||
/*khash_t(is32) MTRC_METHODS */
|
||||
|
||||
/* Maps numeric data keys to uint8_t values.
|
||||
* 1 -> 1
|
||||
* 2 -> 1
|
||||
*/
|
||||
/*khash_t(is32) MTRC_PROTOCOLS */
|
||||
|
||||
/* Maps numeric unique data keys (e.g., 192.168.0.1 => 1) to the unique user
|
||||
* agent key. Therefore, 1 IP can contain multiple user agents
|
||||
* 1 -> 3,5
|
||||
* 2 -> 4,5,6,8
|
||||
*/
|
||||
/*khash_t(igsl) MTRC_AGENTS */
|
||||
|
||||
/* Maps a string key counter such as sum of hits to an autoincremented value
|
||||
* "sum_hits" -> 9383
|
||||
* "sum_bw" -> 3232932
|
||||
*/
|
||||
/*khash_t(igsl) MTRC_METADATA */
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
extern const GKHashMetric module_metrics[];
|
||||
extern const GKHashMetric global_metrics[];
|
||||
extern const size_t global_metrics_len;
|
||||
extern const size_t module_metrics_len;
|
||||
|
||||
char *ht_get_datamap (GModule module, uint32_t key);
|
||||
char *ht_get_host_agent_val (uint32_t key);
|
||||
char *ht_get_method (GModule module, uint32_t key);
|
||||
char *ht_get_protocol (GModule module, uint32_t key);
|
||||
char *ht_get_root (GModule module, uint32_t key);
|
||||
uint32_t ht_get_hits (GModule module, int key);
|
||||
uint32_t ht_get_keymap (GModule module, const char *key);
|
||||
uint32_t ht_get_size_datamap (GModule module);
|
||||
uint32_t ht_get_size_dates (void);
|
||||
uint32_t ht_get_size_uniqmap (GModule module);
|
||||
uint32_t ht_get_visitors (GModule module, uint32_t key);
|
||||
uint32_t ht_sum_valid (void);
|
||||
uint64_t ht_get_bw (GModule module, uint32_t key);
|
||||
uint64_t ht_get_cumts (GModule module, uint32_t key);
|
||||
uint64_t ht_get_maxts (GModule module, uint32_t key);
|
||||
uint64_t ht_get_meta_data (GModule module, const char *key);
|
||||
uint64_t ht_sum_bw (void);
|
||||
void *get_hash (int module, uint64_t key, GSMetric metric);
|
||||
void ht_get_bw_min_max (GModule module, uint64_t * min, uint64_t * max);
|
||||
void ht_get_cumts_min_max (GModule module, uint64_t * min, uint64_t * max);
|
||||
void ht_get_hits_min_max (GModule module, uint32_t * min, uint32_t * max);
|
||||
void ht_get_maxts_min_max (GModule module, uint64_t * min, uint64_t * max);
|
||||
void ht_get_visitors_min_max (GModule module, uint32_t * min, uint32_t * max);
|
||||
|
||||
int ht_inc_cnt_bw (uint32_t date, uint64_t inc);
|
||||
int ht_insert_agent (GModule module, uint32_t date, uint32_t key, uint32_t value);
|
||||
int ht_insert_agent_value (uint32_t date, uint32_t key, char *value);
|
||||
int ht_insert_bw (GModule module, uint32_t date, uint32_t key, uint64_t inc, uint32_t ckey);
|
||||
int ht_insert_cumts (GModule module, uint32_t date, uint32_t key, uint64_t inc, uint32_t ckey);
|
||||
int ht_insert_datamap (GModule module, uint32_t date, uint32_t key, const char *value, uint32_t ckey);
|
||||
int ht_insert_date (uint32_t key);
|
||||
int ht_insert_maxts (GModule module, uint32_t date, uint32_t key, uint64_t value, uint32_t ckey);
|
||||
int ht_insert_method (GModule module, uint32_t date, uint32_t key, const char *value, uint32_t ckey);
|
||||
int ht_insert_protocol (GModule module, uint32_t date, uint32_t key, const char *value, uint32_t ckey);
|
||||
int ht_insert_root (GModule module, uint32_t date, uint32_t key, uint32_t value, uint32_t dkey, uint32_t rkey);
|
||||
int ht_insert_rootmap (GModule module, uint32_t date, uint32_t key, const char *value, uint32_t ckey);
|
||||
int ht_insert_uniqmap (GModule module, uint32_t date, uint32_t key, uint32_t value);
|
||||
uint32_t ht_inc_cnt_valid (uint32_t date, uint32_t inc);
|
||||
uint32_t ht_insert_agent_key (uint32_t date, uint32_t key);
|
||||
uint32_t ht_insert_hits (GModule module, uint32_t date, uint32_t key, uint32_t inc, uint32_t ckey);
|
||||
uint32_t ht_insert_keymap (GModule module, uint32_t date, uint32_t key, uint32_t * ckey);
|
||||
uint32_t ht_insert_unique_key (uint32_t date, const char *key);
|
||||
uint32_t ht_insert_visitor (GModule module, uint32_t date, uint32_t key, uint32_t inc, uint32_t ckey);
|
||||
int ht_insert_meta_data (GModule module, uint32_t date, const char *key, uint64_t value);
|
||||
|
||||
int invalidate_date (int date);
|
||||
int rebuild_rawdata_cache (void);
|
||||
void des_igkh (void *h);
|
||||
void free_cache (GKHashModule * cache);
|
||||
void init_storage (void);
|
||||
|
||||
GRawData *parse_raw_data (GModule module);
|
||||
GSLList *ht_get_host_agent_list (GModule module, uint32_t key);
|
||||
GSLList *ht_get_keymap_list_from_key (GModule module, uint32_t key);
|
||||
/* *INDENT-ON* */
|
||||
|
||||
#endif // for #ifndef GKMHASH_H
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* gmenu.c -- goaccess menus
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "gmenu.h"
|
||||
|
||||
#include "xmalloc.h"
|
||||
#include "ui.h"
|
||||
|
||||
/* Allocate memory for a new GMenu instance.
|
||||
*
|
||||
* On success, the newly allocated GMenu is returned . */
|
||||
GMenu *
|
||||
new_gmenu (WINDOW *parent, int h, int w, int y, int x) {
|
||||
GMenu *menu = xmalloc (sizeof (GMenu));
|
||||
|
||||
memset (menu, 0, sizeof *menu);
|
||||
menu->count = 0;
|
||||
menu->idx = 0;
|
||||
menu->multiple = 0;
|
||||
menu->selectable = 0;
|
||||
menu->start = 0;
|
||||
menu->status = 0;
|
||||
|
||||
menu->h = h;
|
||||
menu->w = w;
|
||||
menu->x = x;
|
||||
menu->y = y;
|
||||
menu->win = derwin (parent, menu->h, menu->w, menu->y, menu->x);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
/* Render actual menu item */
|
||||
static void
|
||||
draw_menu_item (GMenu *menu, char *s, int x, int y, int w, int checked, GColors *(*func) (void)) {
|
||||
char check, *lbl = NULL;
|
||||
|
||||
if (menu->selectable) {
|
||||
check = checked ? 'x' : ' ';
|
||||
lbl = xmalloc (snprintf (NULL, 0, "[%c] %s", check, s) + 1);
|
||||
sprintf (lbl, "[%c] %s", check, s);
|
||||
draw_header (menu->win, lbl, "%s", y, x, w, (*func));
|
||||
free (lbl);
|
||||
} else {
|
||||
draw_header (menu->win, s, "%s", y, x, w, (*func));
|
||||
}
|
||||
}
|
||||
|
||||
/* Displays a menu to its associated window.
|
||||
*
|
||||
* On error, 1 is returned.
|
||||
* On success, the newly created menu is added to the window and 0 is
|
||||
* returned. */
|
||||
int
|
||||
post_gmenu (GMenu *menu) {
|
||||
GColors *(*func) (void);
|
||||
int i = 0, j = 0, start, end, height, total, checked = 0;
|
||||
|
||||
if (menu == NULL)
|
||||
return 1;
|
||||
|
||||
werase (menu->win);
|
||||
|
||||
height = menu->h;
|
||||
start = menu->start;
|
||||
total = menu->size;
|
||||
end = height < total ? start + height : total;
|
||||
|
||||
for (i = start; i < end; i++, j++) {
|
||||
func = i == menu->idx ? color_selected : color_default;
|
||||
checked = menu->items[i].checked ? 1 : 0;
|
||||
draw_menu_item (menu, menu->items[i].name, 0, j, menu->w, checked, func);
|
||||
}
|
||||
wrefresh (menu->win);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Main work horse of the menu system processing input events */
|
||||
void
|
||||
gmenu_driver (GMenu *menu, int c) {
|
||||
int i;
|
||||
|
||||
switch (c) {
|
||||
case REQ_DOWN:
|
||||
if (menu->idx >= menu->size - 1)
|
||||
break;
|
||||
++menu->idx;
|
||||
if (menu->idx >= menu->h && menu->idx >= menu->start + menu->h)
|
||||
menu->start++;
|
||||
post_gmenu (menu);
|
||||
break;
|
||||
case REQ_UP:
|
||||
if (menu->idx <= 0)
|
||||
break;
|
||||
--menu->idx;
|
||||
if (menu->idx < menu->start)
|
||||
--menu->start;
|
||||
post_gmenu (menu);
|
||||
break;
|
||||
case REQ_SEL:
|
||||
if (!menu->multiple) {
|
||||
for (i = 0; i < menu->size; i++)
|
||||
menu->items[i].checked = 0;
|
||||
}
|
||||
if (menu->items[menu->idx].checked)
|
||||
menu->items[menu->idx].checked = 0;
|
||||
else
|
||||
menu->items[menu->idx].checked = 1;
|
||||
post_gmenu (menu);
|
||||
break;
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_NCURSESW_NCURSES_H
|
||||
#include <ncursesw/ncurses.h>
|
||||
#elif HAVE_NCURSES_NCURSES_H
|
||||
#include <ncurses/ncurses.h>
|
||||
#elif HAVE_NCURSES_H
|
||||
#include <ncurses.h>
|
||||
#elif HAVE_CURSES_H
|
||||
#include <curses.h>
|
||||
#endif
|
||||
|
||||
#ifndef GMENU_H_INCLUDED
|
||||
#define GMENU_H_INCLUDED
|
||||
|
||||
enum ACTION {
|
||||
REQ_DOWN,
|
||||
REQ_UP,
|
||||
REQ_SEL
|
||||
};
|
||||
|
||||
typedef struct GMenu_ GMenu;
|
||||
typedef struct GItem_ GItem;
|
||||
|
||||
/* Menu Item */
|
||||
struct GItem_ {
|
||||
char *name;
|
||||
int checked;
|
||||
};
|
||||
|
||||
/* Menu Panel */
|
||||
struct GMenu_ {
|
||||
WINDOW *win;
|
||||
|
||||
int count;
|
||||
int size;
|
||||
int idx;
|
||||
int start;
|
||||
int h;
|
||||
int w;
|
||||
int x;
|
||||
int y;
|
||||
unsigned short multiple;
|
||||
unsigned short selectable;
|
||||
unsigned short status;
|
||||
GItem *items;
|
||||
};
|
||||
|
||||
GMenu *new_gmenu (WINDOW * parent, int h, int w, int y, int x);
|
||||
int post_gmenu (GMenu * menu);
|
||||
void gmenu_driver (GMenu * menu, int c);
|
||||
|
||||
#endif
|
||||
+1900
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GOACCESS_H_INCLUDED
|
||||
#define GOACCESS_H_INCLUDED
|
||||
|
||||
#include "ui.h"
|
||||
#include "dialogs.h"
|
||||
|
||||
#define RAND_FN 7 + 1
|
||||
|
||||
extern GSpinner *parsing_spinner;
|
||||
extern int active_gdns; /* kill dns pthread flag */
|
||||
|
||||
void read_client (void *ptr_data);
|
||||
|
||||
#endif
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* gslist.c -- A Singly link list implementation
|
||||
* _______ _______ __ __
|
||||
* / ____/ | / / ___/____ _____/ /_____ / /_
|
||||
* / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/
|
||||
* / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_
|
||||
* \____/ |__/|__//____/\____/\___/_/|_|\___/\__/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "gslist.h"
|
||||
#include "gstorage.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
/* Instantiate a new Singly linked-list node.
|
||||
*
|
||||
* On error, aborts if node can't be malloc'd.
|
||||
* On success, the GSLList node. */
|
||||
GSLList *
|
||||
list_create (void *data) {
|
||||
GSLList *node = xmalloc (sizeof (GSLList));
|
||||
node->data = data;
|
||||
node->next = NULL;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/* Create and insert a node after a given node.
|
||||
*
|
||||
* On error, aborts if node can't be malloc'd.
|
||||
* On success, the newly created node. */
|
||||
GSLList *
|
||||
list_insert_append (GSLList *node, void *data) {
|
||||
GSLList *newnode;
|
||||
newnode = list_create (data);
|
||||
newnode->next = node->next;
|
||||
node->next = newnode;
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
||||
/* Create and insert a node in front of the list.
|
||||
*
|
||||
* On error, aborts if node can't be malloc'd.
|
||||
* On success, the newly created node. */
|
||||
GSLList *
|
||||
list_insert_prepend (GSLList *list, void *data) {
|
||||
GSLList *newnode;
|
||||
newnode = list_create (data);
|
||||
newnode->next = list;
|
||||
|
||||
return newnode;
|
||||
}
|
||||
|
||||
/* Find a node given a pointer to a function that compares them.
|
||||
*
|
||||
* If comparison fails, NULL is returned.
|
||||
* On success, the existing node is returned. */
|
||||
GSLList *
|
||||
list_find (GSLList *node, int (*func) (void *, void *), void *data) {
|
||||
while (node) {
|
||||
if (func (node->data, data) > 0)
|
||||
return node;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GSLList *
|
||||
list_copy (GSLList *node) {
|
||||
GSLList *list = NULL;
|
||||
|
||||
while (node) {
|
||||
if (!list)
|
||||
list = list_create (i322ptr ((*(uint32_t *) node->data)));
|
||||
else
|
||||
list = list_insert_prepend (list, i322ptr ((*(uint32_t *) node->data)));
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/* Remove all nodes from the list.
|
||||
*
|
||||
* On success, 0 is returned. */
|
||||
int
|
||||
list_remove_nodes (GSLList *list) {
|
||||
GSLList *tmp;
|
||||
while (list != NULL) {
|
||||
tmp = list->next;
|
||||
if (list->data)
|
||||
free (list->data);
|
||||
free (list);
|
||||
list = tmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Remove the given node from the list.
|
||||
*
|
||||
* On error, 1 is returned.
|
||||
* On success, 0 is returned. */
|
||||
int
|
||||
list_remove_node (GSLList **list, GSLList *node) {
|
||||
GSLList **current = list, *next = NULL;
|
||||
for (; *current; current = &(*current)->next) {
|
||||
if ((*current) != node)
|
||||
continue;
|
||||
|
||||
next = (*current)->next;
|
||||
if ((*current)->data)
|
||||
free ((*current)->data);
|
||||
free (*current);
|
||||
*current = next;
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Iterate over the single linked-list and call function pointer.
|
||||
*
|
||||
* If function pointer does not return 0, -1 is returned.
|
||||
* On success, 0 is returned. */
|
||||
int
|
||||
list_foreach (GSLList *node, int (*func) (void *, void *), void *user_data) {
|
||||
while (node) {
|
||||
if (func (node->data, user_data) != 0)
|
||||
return -1;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Count the number of elements on the linked-list.
|
||||
*
|
||||
* On success, the number of elements is returned. */
|
||||
int
|
||||
list_count (GSLList *node) {
|
||||
int count = 0;
|
||||
while (node != 0) {
|
||||
count++;
|
||||
node = node->next;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* _______ _______ __ __
|
||||
* / ____/ | / / ___/____ _____/ /_____ / /_
|
||||
* / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/
|
||||
* / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_
|
||||
* \____/ |__/|__//____/\____/\___/_/|_|\___/\__/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GSLIST_H_INCLUDED
|
||||
#define GSLIST_H_INCLUDED
|
||||
|
||||
/* Generic Single linked-list */
|
||||
typedef struct GSLList_ {
|
||||
void *data;
|
||||
struct GSLList_ *next;
|
||||
} GSLList;
|
||||
|
||||
#define GSLIST_FOREACH(node, data, code) { \
|
||||
GSLList *__tmp = node; \
|
||||
while (__tmp) { \
|
||||
(data) = __tmp->data; \
|
||||
code; \
|
||||
__tmp = __tmp->next; \
|
||||
}}
|
||||
|
||||
/* single linked-list */
|
||||
GSLList *list_create (void *data);
|
||||
GSLList *list_find (GSLList * node, int (*func) (void *, void *), void *data);
|
||||
GSLList *list_insert_append (GSLList * node, void *data);
|
||||
GSLList *list_insert_prepend (GSLList * list, void *data);
|
||||
GSLList *list_copy (GSLList * node);
|
||||
int list_count (GSLList * list);
|
||||
int list_foreach (GSLList * node, int (*func) (void *, void *), void *user_data);
|
||||
int list_remove_node (GSLList ** list, GSLList * node);
|
||||
int list_remove_nodes (GSLList * list);
|
||||
|
||||
#endif // for #ifndef GSLIST_H
|
||||
+1642
File diff suppressed because it is too large
Load Diff
+167
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GSTORAGE_H_INCLUDED
|
||||
#define GSTORAGE_H_INCLUDED
|
||||
|
||||
#include "commons.h"
|
||||
#include "parser.h"
|
||||
|
||||
#define DB_PATH "/tmp"
|
||||
|
||||
#define GAMTRC_TOTAL 8
|
||||
/* Enumerated App Metrics */
|
||||
typedef enum GAMetric_ {
|
||||
MTRC_DATES,
|
||||
MTRC_SEQS,
|
||||
MTRC_CNT_OVERALL,
|
||||
MTRC_HOSTNAMES,
|
||||
MTRC_LAST_PARSE,
|
||||
MTRC_JSON_LOGFMT,
|
||||
MTRC_METH_PROTO,
|
||||
MTRC_DB_PROPS,
|
||||
} GAMetric;
|
||||
|
||||
/* Enumerated Storage Metrics */
|
||||
typedef enum GSMetricType_ {
|
||||
/* uint32_t key - uint32_t val */
|
||||
MTRC_TYPE_II32,
|
||||
/* uint32_t key - string val */
|
||||
MTRC_TYPE_IS32,
|
||||
/* uint32_t key - uint64_t val */
|
||||
MTRC_TYPE_IU64,
|
||||
/* string key - uint32_t val */
|
||||
MTRC_TYPE_SI32,
|
||||
/* string key - uint8_t val */
|
||||
MTRC_TYPE_SI08,
|
||||
/* uint32_t key - uint8_t val */
|
||||
MTRC_TYPE_II08,
|
||||
/* string key - string val */
|
||||
MTRC_TYPE_SS32,
|
||||
/* uint32_t key - GSLList val */
|
||||
MTRC_TYPE_IGSL,
|
||||
/* string key - uint64_t val */
|
||||
MTRC_TYPE_SU64,
|
||||
/* uint32_t key - GKHashStorage_ val */
|
||||
MTRC_TYPE_IGKH,
|
||||
/* uint64_t key - uint32_t val */
|
||||
MTRC_TYPE_U648,
|
||||
/* uint64_t key - GLastParse val */
|
||||
MTRC_TYPE_IGLP,
|
||||
} GSMetricType;
|
||||
|
||||
typedef struct GKHashMetric_ {
|
||||
union {
|
||||
GSMetric storem;
|
||||
GAMetric dbm;
|
||||
} metric;
|
||||
GSMetricType type;
|
||||
void *(*alloc) (void);
|
||||
void (*des) (void *, uint8_t free_data);
|
||||
void (*del) (void *, uint8_t free_data);
|
||||
uint8_t free_data:1;
|
||||
void *hash;
|
||||
const char *filename;
|
||||
} GKHashMetric;
|
||||
|
||||
/* Each record contains a data value, i.e., Windows XP, and it may contain a
|
||||
* root value, i.e., Windows, and a unique key which is the combination of
|
||||
* date, IP and user agent */
|
||||
typedef struct GKeyData_ {
|
||||
const void *data;
|
||||
uint32_t dhash;
|
||||
uint32_t data_nkey;
|
||||
uint32_t cdnkey; /* cache data nkey */
|
||||
|
||||
uint32_t rhash;
|
||||
const void *root;
|
||||
const void *root_key;
|
||||
uint32_t root_nkey;
|
||||
uint32_t crnkey; /* cache root nkey */
|
||||
|
||||
void *uniq_key;
|
||||
uint32_t uniq_nkey;
|
||||
|
||||
uint32_t numdate;
|
||||
} GKeyData;
|
||||
|
||||
typedef struct GParse_ {
|
||||
GModule module;
|
||||
int (*key_data) (GKeyData * kdata, GLogItem * logitem);
|
||||
|
||||
/* data field */
|
||||
void (*datamap) (GModule module, GKeyData * kdata);
|
||||
void (*rootmap) (GModule module, GKeyData * kdata);
|
||||
void (*hits) (GModule module, GKeyData * kdata);
|
||||
void (*visitor) (GModule module, GKeyData * kdata);
|
||||
void (*bw) (GModule module, GKeyData * kdata, uint64_t size);
|
||||
void (*cumts) (GModule module, GKeyData * kdata, uint64_t ts);
|
||||
void (*maxts) (GModule module, GKeyData * kdata, uint64_t ts);
|
||||
void (*method) (GModule module, GKeyData * kdata, const char *data);
|
||||
void (*protocol) (GModule module, GKeyData * kdata, const char *data);
|
||||
void (*agent) (GModule module, GKeyData * kdata, uint32_t agent_nkey);
|
||||
} GParse;
|
||||
|
||||
typedef struct httpmethods_ {
|
||||
const char *method;
|
||||
int len;
|
||||
} httpmethods;
|
||||
|
||||
typedef struct httpprotocols_ {
|
||||
const char *protocol;
|
||||
int len;
|
||||
} httpprotocols;
|
||||
|
||||
extern const httpmethods http_methods[];
|
||||
extern const httpprotocols http_protocols[];
|
||||
extern const size_t http_methods_len;
|
||||
extern const size_t http_protocols_len;
|
||||
|
||||
const char *get_mtr_str (GSMetric metric);
|
||||
int excluded_ip (GLogItem * logitem);
|
||||
uint32_t *i322ptr (uint32_t val);
|
||||
uint64_t *uint642ptr (uint64_t val);
|
||||
void count_process_and_invalid (GLog * glog, GLogItem * logitem, const char *line);
|
||||
void count_process (GLog * glog);
|
||||
void free_gmetrics (GMetrics * metric);
|
||||
void insert_methods_protocols (void);
|
||||
void process_log (GLogItem * logitem);
|
||||
void set_browser_os (GLogItem * logitem);
|
||||
void set_data_metrics (GMetrics * ometrics, GMetrics ** nmetrics, GPercTotals totals);
|
||||
void set_module_totals (GPercTotals * totals);
|
||||
void uncount_invalid (GLog * glog);
|
||||
void uncount_processed (GLog * glog);
|
||||
GMetrics *new_gmetrics (void);
|
||||
|
||||
#ifdef HAVE_GEOLOCATION
|
||||
const char *get_continent_for_country (const char *country);
|
||||
void free_country_continent_map (void);
|
||||
#endif
|
||||
|
||||
#endif // for #ifndef GSTORAGE_H
|
||||
+545
@@ -0,0 +1,545 @@
|
||||
/**
|
||||
* gwsocket.c -- An interface to send/recv data from/to Web Socket Server
|
||||
* _______ _______ __ __
|
||||
* / ____/ | / / ___/____ _____/ /_____ / /_
|
||||
* / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/
|
||||
* / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_
|
||||
* \____/ |__/|__//____/\____/\___/_/|_|\___/\__/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "gwsocket.h"
|
||||
|
||||
#include "commons.h"
|
||||
#include "error.h"
|
||||
#include "goaccess.h"
|
||||
#include "pdjson.h"
|
||||
#include "json.h"
|
||||
#include "settings.h"
|
||||
#include "websocket.h"
|
||||
#include "wsauth.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
/* Allocate memory for a new GWSReader instance.
|
||||
*
|
||||
* On success, the newly allocated GWSReader is returned. */
|
||||
GWSReader *
|
||||
new_gwsreader (void) {
|
||||
GWSReader *reader = xmalloc (sizeof (GWSReader));
|
||||
memset (reader, 0, sizeof *reader);
|
||||
|
||||
return reader;
|
||||
}
|
||||
|
||||
/* Allocate memory for a new GWSWriter instance.
|
||||
*
|
||||
* On success, the newly allocated GWSWriter is returned. */
|
||||
GWSWriter *
|
||||
new_gwswriter (void) {
|
||||
GWSWriter *writer = xmalloc (sizeof (GWSWriter));
|
||||
memset (writer, 0, sizeof *writer);
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
/* Write the JSON data to a pipe.
|
||||
*
|
||||
* If unable to write bytes, -1 is returned.
|
||||
* On success, the number of written bytes is returned . */
|
||||
static int
|
||||
write_holder (int fd, const char *buf, int len) {
|
||||
int i, ret = 0;
|
||||
|
||||
for (i = 0; i < len;) {
|
||||
ret = write (fd, buf + i, len - i);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR || errno == EAGAIN)
|
||||
continue;
|
||||
return -1;
|
||||
} else {
|
||||
i += ret;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Clear an incoming FIFO packet and header data. */
|
||||
static void
|
||||
clear_fifo_packet (GWSReader *gwserver) {
|
||||
memset (gwserver->hdr, 0, sizeof (gwserver->hdr));
|
||||
gwserver->hlen = 0;
|
||||
|
||||
if (gwserver->packet == NULL)
|
||||
return;
|
||||
|
||||
if (gwserver->packet->data)
|
||||
free (gwserver->packet->data);
|
||||
free (gwserver->packet);
|
||||
gwserver->packet = NULL;
|
||||
}
|
||||
|
||||
/* Pack the JSON data into a network byte order and writes it to a
|
||||
* pipe.
|
||||
*
|
||||
* On success, 0 is returned . */
|
||||
int
|
||||
broadcast_holder (int fd, const char *buf, int len) {
|
||||
char *p = NULL, *ptr = NULL;
|
||||
|
||||
p = calloc (sizeof (uint32_t) * 3, sizeof (char));
|
||||
|
||||
ptr = p;
|
||||
ptr += pack_uint32 (ptr, 0);
|
||||
ptr += pack_uint32 (ptr, 0x01);
|
||||
ptr += pack_uint32 (ptr, len);
|
||||
|
||||
write_holder (fd, p, sizeof (uint32_t) * 3);
|
||||
write_holder (fd, buf, len);
|
||||
free (p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Pack the JSON data into a network byte order and write it to a
|
||||
* pipe.
|
||||
*
|
||||
* On success, 0 is returned . */
|
||||
int
|
||||
send_holder_to_client (int fd, int listener, const char *buf, int len) {
|
||||
char *p = NULL, *ptr = NULL;
|
||||
|
||||
p = calloc (sizeof (uint32_t) * 3, sizeof (char));
|
||||
|
||||
ptr = p;
|
||||
ptr += pack_uint32 (ptr, listener);
|
||||
ptr += pack_uint32 (ptr, 0x01);
|
||||
ptr += pack_uint32 (ptr, len);
|
||||
|
||||
write_holder (fd, p, sizeof (uint32_t) * 3);
|
||||
write_holder (fd, buf, len);
|
||||
free (p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Attempt to read data from the named pipe on strict mode.
|
||||
* Note: For now it only reads on new connections, i.e., onopen.
|
||||
*
|
||||
* If there's less data than requested, 0 is returned
|
||||
* If the thread is done, 1 is returned */
|
||||
int
|
||||
read_fifo (GWSReader *gwsreader, void (*f) (int)) {
|
||||
WSPacket **pa = &gwsreader->packet;
|
||||
char *ptr;
|
||||
int bytes = 0, readh = 0, need = 0, fd = gwsreader->fd;
|
||||
uint32_t listener = 0, type = 0, size = 0;
|
||||
struct pollfd fds[] = {
|
||||
{.fd = gwsreader->self_pipe[0],.events = POLLIN},
|
||||
{.fd = gwsreader->fd,.events = POLLIN,},
|
||||
};
|
||||
|
||||
if (poll (fds, sizeof (fds) / sizeof (fds[0]), -1) == -1) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
break;
|
||||
default:
|
||||
FATAL ("Unable to poll: %s.", strerror (errno));
|
||||
}
|
||||
}
|
||||
/* handle self-pipe trick */
|
||||
if (fds[0].revents & POLLIN)
|
||||
return 1;
|
||||
if (!(fds[1].revents & POLLIN)) {
|
||||
LOG (("No file descriptor set on read_message()\n"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
readh = gwsreader->hlen; /* read from header so far */
|
||||
need = HDR_SIZE - readh; /* need to read */
|
||||
if (need > 0) {
|
||||
if ((bytes = ws_read_fifo (fd, gwsreader->hdr, &gwsreader->hlen, readh, need)) < 0)
|
||||
return 0;
|
||||
if (bytes != need)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* unpack size, and type */
|
||||
ptr = gwsreader->hdr;
|
||||
ptr += unpack_uint32 (ptr, &listener);
|
||||
ptr += unpack_uint32 (ptr, &type);
|
||||
ptr += unpack_uint32 (ptr, &size);
|
||||
|
||||
if ((*pa) == NULL) {
|
||||
(*pa) = xcalloc (1, sizeof (WSPacket));
|
||||
(*pa)->type = type;
|
||||
(*pa)->size = size;
|
||||
(*pa)->data = xcalloc (size + 1, sizeof (char));
|
||||
}
|
||||
|
||||
readh = (*pa)->len; /* read from payload so far */
|
||||
need = (*pa)->size - readh; /* need to read */
|
||||
if (need > 0) {
|
||||
if ((bytes = ws_read_fifo (fd, (*pa)->data, &(*pa)->len, readh, need)) < 0)
|
||||
return 0;
|
||||
if (bytes != need)
|
||||
return 0;
|
||||
}
|
||||
clear_fifo_packet (gwsreader);
|
||||
/* fast forward JSON data to the given client */
|
||||
(*f) (listener);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Callback once a new connection is established
|
||||
*
|
||||
* It writes to a named pipe a header containing the socket, the
|
||||
* message type, the payload's length and the actual payload */
|
||||
static int
|
||||
onopen (WSPipeOut *pipeout, WSClient *client) {
|
||||
uint32_t hsize = sizeof (uint32_t) * 3;
|
||||
char *hdr = calloc (hsize, sizeof (char));
|
||||
char *ptr = hdr;
|
||||
|
||||
ptr += pack_uint32 (ptr, client->listener);
|
||||
ptr += pack_uint32 (ptr, WS_OPCODE_TEXT);
|
||||
ptr += pack_uint32 (ptr, INET6_ADDRSTRLEN);
|
||||
|
||||
ws_write_fifo (pipeout, hdr, hsize);
|
||||
ws_write_fifo (pipeout, client->remote_ip, INET6_ADDRSTRLEN);
|
||||
free (hdr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* validate_token_message()
|
||||
*
|
||||
* Parses a JSON payload (assumed to be null-terminated) that should have
|
||||
* the form: {"action":"validate_token","token":"..."}
|
||||
* If the action is "validate_token", the token is verified using verify_jwt_token().
|
||||
* If verification is successful, the client's stored JWT is updated.
|
||||
*
|
||||
* Returns:
|
||||
* 1 if the message was a token validation message and authentication succeeded.
|
||||
* 0 if the message is not a token validation message.
|
||||
* -1 if an error occurs (including validation failure).
|
||||
*/
|
||||
#ifdef HAVE_LIBSSL
|
||||
static int
|
||||
validate_token_message (const char *payload, WSClient *client) {
|
||||
json_stream json;
|
||||
enum json_type t = JSON_ERROR;
|
||||
size_t len = 0, level = 0;
|
||||
enum json_type ctx = JSON_ERROR;
|
||||
char *curr_key = NULL;
|
||||
char *action = NULL;
|
||||
char *token = NULL;
|
||||
|
||||
json_open_string (&json, payload);
|
||||
json_set_streaming (&json, false);
|
||||
|
||||
/* Expect a JSON object */
|
||||
t = json_next (&json);
|
||||
if (t != JSON_OBJECT) {
|
||||
json_close (&json);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Iterate over the JSON tokens */
|
||||
while ((t = json_next (&json)) != JSON_DONE && t != JSON_ERROR) {
|
||||
ctx = json_get_context (&json, &level);
|
||||
/* When (level % 2) != 0 and not in an array, the token is a key */
|
||||
if ((level % 2) != 0 && ctx != JSON_ARRAY) {
|
||||
if (curr_key)
|
||||
free (curr_key);
|
||||
curr_key = xstrdup (json_get_string (&json, &len));
|
||||
} else {
|
||||
/* Otherwise, token is a value for the last encountered key */
|
||||
if (curr_key) {
|
||||
char *val = xstrdup (json_get_string (&json, &len));
|
||||
if (strcmp (curr_key, "action") == 0) {
|
||||
action = val;
|
||||
} else if (strcmp (curr_key, "token") == 0) {
|
||||
token = val;
|
||||
} else {
|
||||
free (val);
|
||||
}
|
||||
free (curr_key);
|
||||
curr_key = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (curr_key)
|
||||
free (curr_key);
|
||||
json_close (&json);
|
||||
|
||||
/* If action is not "validate_token", then this message is not for token validation */
|
||||
if (!action || strcmp (action, "validate_token") != 0) {
|
||||
if (action)
|
||||
free (action);
|
||||
if (token)
|
||||
free (token);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* For token validation, the token must exist */
|
||||
if (!token) {
|
||||
LOG (("Missing token in validate_token message from client %d [%s]\n", client->listener,
|
||||
client->remote_ip));
|
||||
free (action);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Verify the token using the configured secret */
|
||||
if (conf.ws_auth_secret && verify_jwt_token (token, conf.ws_auth_secret) != 1) {
|
||||
LOG (("Authentication failed for client %d [%s]\n", client->listener, client->remote_ip));
|
||||
free (action);
|
||||
free (token);
|
||||
client->status = WS_ERR | WS_CLOSE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Authentication succeeded: update client's stored token */
|
||||
if (client->headers->jwt)
|
||||
free (client->headers->jwt);
|
||||
|
||||
client->headers->jwt = strdup (token);
|
||||
LOG (("Token validated and updated for client %d [%s]\n", client->listener, client->remote_ip));
|
||||
|
||||
free (action);
|
||||
free (token);
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* onmessage()
|
||||
*
|
||||
* Entry point for incoming messages. This function first checks if the message
|
||||
* is a text message and ensures that the payload is null-terminated (so JSON
|
||||
* parsing will work correctly). It then delegates token validation to
|
||||
* validate_token_message(). In the future, onmessage() may handle other message
|
||||
* types.
|
||||
*/
|
||||
#ifdef HAVE_LIBSSL
|
||||
static int
|
||||
onmessage (GO_UNUSED WSPipeOut *pipeout, WSClient *client) {
|
||||
int ret = 1;
|
||||
char *payload = client->message->payload;
|
||||
int allocated = 0;
|
||||
|
||||
/* If this is a text message, ensure the payload is null-terminated.
|
||||
* Binary frames should not be modified.
|
||||
*/
|
||||
if (client->message->opcode == WS_OPCODE_TEXT) {
|
||||
if (memchr (payload, '\0', client->message->payloadsz) == NULL) {
|
||||
char *tmp = malloc (client->message->payloadsz + 1);
|
||||
if (!tmp) {
|
||||
LOG (("Memory allocation error for client %d [%s]\n", client->listener, client->remote_ip));
|
||||
return -1;
|
||||
}
|
||||
memcpy (tmp, payload, client->message->payloadsz);
|
||||
tmp[client->message->payloadsz] = '\0';
|
||||
payload = tmp;
|
||||
allocated = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Delegate processing to validate_token_message().
|
||||
* In the future, you can add additional branches for other message types.
|
||||
*/
|
||||
ret = validate_token_message (payload, client);
|
||||
|
||||
if (allocated)
|
||||
free (payload);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Done parsing, clear out line and set status message. */
|
||||
void
|
||||
set_ready_state (void) {
|
||||
fprintf (stderr, "\33[2K\r");
|
||||
fprintf (stderr, "%s\n", INFO_WS_READY_FOR_CONN);
|
||||
}
|
||||
|
||||
/* Open the named pipe where the websocket server writes to.
|
||||
*
|
||||
* If unable to open, -1 is returned.
|
||||
* On success, return the new file descriptor is returned . */
|
||||
int
|
||||
open_fifoout (void) {
|
||||
const char *fifo = conf.fifo_out;
|
||||
int fdfifo;
|
||||
|
||||
/* open fifo for reading before writing */
|
||||
ws_setfifo (fifo);
|
||||
if ((fdfifo = open (fifo, O_RDWR | O_NONBLOCK)) == -1)
|
||||
return -1;
|
||||
|
||||
return fdfifo;
|
||||
}
|
||||
|
||||
/* Open the named pipe where the websocket server reads from.
|
||||
*
|
||||
* If unable to open, -1 is returned.
|
||||
* On success, return the new file descriptor is returned . */
|
||||
int
|
||||
open_fifoin (void) {
|
||||
const char *fifo = conf.fifo_in;
|
||||
int fdfifo;
|
||||
|
||||
if ((fdfifo = open (fifo, O_WRONLY | O_NONBLOCK)) == -1)
|
||||
return -1;
|
||||
|
||||
return fdfifo;
|
||||
}
|
||||
|
||||
/* Set the self-pipe trick to handle poll(2). */
|
||||
void
|
||||
set_self_pipe (int *self_pipe) {
|
||||
/* Initialize self pipe. */
|
||||
if (pipe (self_pipe) == -1)
|
||||
FATAL ("Unable to create pipe: %s.", strerror (errno));
|
||||
|
||||
/* make the read and write pipe non-blocking */
|
||||
set_nonblocking (self_pipe[0]);
|
||||
set_nonblocking (self_pipe[1]);
|
||||
}
|
||||
|
||||
/* Close the WebSocket server and clean up. */
|
||||
void
|
||||
stop_ws_server (GWSWriter *gwswriter, GWSReader *gwsreader) {
|
||||
pthread_t writer, reader;
|
||||
WSServer *server = NULL;
|
||||
|
||||
if (!gwsreader || !gwswriter)
|
||||
return;
|
||||
if (!(server = gwswriter->server))
|
||||
return;
|
||||
|
||||
pthread_mutex_lock (&gwsreader->mutex);
|
||||
if ((write (gwsreader->self_pipe[1], "x", 1)) == -1 && errno != EAGAIN)
|
||||
LOG (("Unable to write to self pipe on pipeout.\n"));
|
||||
pthread_mutex_unlock (&gwsreader->mutex);
|
||||
|
||||
/* if it fails to write, force stop */
|
||||
pthread_mutex_lock (&gwswriter->mutex);
|
||||
if ((write (server->self_pipe[1], "x", 1)) == -1 && errno != EAGAIN)
|
||||
ws_stop (server);
|
||||
pthread_mutex_unlock (&gwswriter->mutex);
|
||||
|
||||
reader = gwsreader->thread;
|
||||
if (pthread_join (reader, NULL) != 0)
|
||||
LOG (("Unable to join thread gwsreader: %s\n", strerror (errno)));
|
||||
|
||||
writer = gwswriter->thread;
|
||||
if (pthread_join (writer, NULL) != 0)
|
||||
LOG (("Unable to join thread gwswriter: %s\n", strerror (errno)));
|
||||
}
|
||||
|
||||
/* Start the WebSocket server and initialize default options. */
|
||||
static void
|
||||
start_server (void *ptr_data) {
|
||||
GWSWriter *writer = (GWSWriter *) ptr_data;
|
||||
|
||||
writer->server->onopen = onopen;
|
||||
#ifdef HAVE_LIBSSL
|
||||
writer->server->onmessage = onmessage;
|
||||
#endif
|
||||
|
||||
pthread_mutex_lock (&writer->mutex);
|
||||
set_self_pipe (writer->server->self_pipe);
|
||||
pthread_mutex_unlock (&writer->mutex);
|
||||
|
||||
/* poll(2) will block in here */
|
||||
ws_start (writer->server);
|
||||
fprintf (stderr, "Stopping WebSocket server...\n");
|
||||
ws_stop (writer->server);
|
||||
}
|
||||
|
||||
/* Read and set the WebSocket config options. */
|
||||
static void
|
||||
set_ws_opts (void) {
|
||||
ws_set_config_strict (1);
|
||||
if (conf.addr)
|
||||
ws_set_config_host (conf.addr);
|
||||
if (conf.unix_socket)
|
||||
ws_set_config_unix_socket (conf.unix_socket);
|
||||
if (conf.fifo_in)
|
||||
ws_set_config_pipein (conf.fifo_in);
|
||||
if (conf.fifo_out)
|
||||
ws_set_config_pipeout (conf.fifo_out);
|
||||
if (conf.origin)
|
||||
ws_set_config_origin (conf.origin);
|
||||
if (conf.port)
|
||||
ws_set_config_port (conf.port);
|
||||
if (conf.sslcert)
|
||||
ws_set_config_sslcert (conf.sslcert);
|
||||
if (conf.sslkey)
|
||||
ws_set_config_sslkey (conf.sslkey);
|
||||
#ifdef HAVE_LIBSSL
|
||||
if (conf.ws_auth_secret) {
|
||||
ws_set_config_auth_secret (conf.ws_auth_secret);
|
||||
ws_set_config_auth_cb (verify_jwt_token);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Setup and start the WebSocket threads. */
|
||||
int
|
||||
setup_ws_server (GWSWriter *gwswriter, GWSReader *gwsreader) {
|
||||
int id;
|
||||
pthread_t *thread;
|
||||
|
||||
if (pthread_mutex_init (&gwswriter->mutex, NULL))
|
||||
FATAL ("Failed init gwswriter mutex");
|
||||
if (pthread_mutex_init (&gwsreader->mutex, NULL))
|
||||
FATAL ("Failed init gwsreader mutex");
|
||||
|
||||
/* send WS data thread */
|
||||
thread = &gwswriter->thread;
|
||||
|
||||
/* pre-init the websocket server, to ensure the FIFOs are created */
|
||||
if ((gwswriter->server = ws_init ("0.0.0.0", "7890", set_ws_opts)) == NULL)
|
||||
FATAL ("Failed init websocket");
|
||||
|
||||
id = pthread_create (&(*thread), NULL, (void *) &start_server, gwswriter);
|
||||
if (id)
|
||||
FATAL ("Return code from pthread_create(): %d", id);
|
||||
|
||||
/* read WS data thread */
|
||||
thread = &gwsreader->thread;
|
||||
id = pthread_create (&(*thread), NULL, (void *) &read_client, gwsreader);
|
||||
if (id)
|
||||
FATAL ("Return code from pthread_create(): %d", id);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* _______ _______ __ __
|
||||
* / ____/ | / / ___/____ _____/ /_____ / /_
|
||||
* / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/
|
||||
* / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_
|
||||
* \____/ |__/|__//____/\____/\___/_/|_|\___/\__/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GWSOCKET_H_INCLUDED
|
||||
#define GWSOCKET_H_INCLUDED
|
||||
|
||||
#define GW_VERSION "0.1"
|
||||
|
||||
#include <pthread.h>
|
||||
#include "websocket.h"
|
||||
|
||||
typedef struct GWSReader_ {
|
||||
int fd;
|
||||
int self_pipe[2]; /* self-pipe */
|
||||
|
||||
pthread_mutex_t mutex; /* Mutex fifo out */
|
||||
pthread_t thread; /* Thread fifo in */
|
||||
|
||||
WSPacket *packet; /* FIFO data's buffer */
|
||||
char hdr[HDR_SIZE]; /* FIFO header's buffer */
|
||||
int hlen; /* header length */
|
||||
} GWSReader;
|
||||
|
||||
typedef struct GWSWriter_ {
|
||||
int fd;
|
||||
|
||||
pthread_mutex_t mutex; /* Mutex fifo in */
|
||||
pthread_t thread; /* Thread fifo out */
|
||||
|
||||
WSServer *server; /* WebSocket server */
|
||||
} GWSWriter;
|
||||
|
||||
GWSReader *new_gwsreader (void);
|
||||
GWSWriter *new_gwswriter (void);
|
||||
int broadcast_holder (int fd, const char *buf, int len);
|
||||
int open_fifoin (void);
|
||||
int open_fifoout (void);
|
||||
int read_fifo (GWSReader * gwsreader, void (*f) (int));
|
||||
int send_holder_to_client (int fd, int listener, const char *buf, int len);
|
||||
int setup_ws_server (GWSWriter * gwswriter, GWSReader * gwsreader);
|
||||
void set_ready_state (void);
|
||||
void set_self_pipe (int *self_pipe);
|
||||
void stop_ws_server (GWSWriter * gwswriter, GWSReader * gwsreader);
|
||||
|
||||
#endif // for #ifndef GWSOCKET_H
|
||||
+1203
File diff suppressed because it is too large
Load Diff
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* ______ ___
|
||||
* / ____/___ / | _____________ __________
|
||||
* / / __/ __ \/ /| |/ ___/ ___/ _ \/ ___/ ___/
|
||||
* / /_/ / /_/ / ___ / /__/ /__/ __(__ |__ )
|
||||
* \____/\____/_/ |_\___/\___/\___/____/____/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2009-2026 Gerardo Orellana <hello @ goaccess.io>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#if HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#ifndef JSON_H_INCLUDED
|
||||
#define JSON_H_INCLUDED
|
||||
|
||||
#define TAB "\t\t\t\t\t\t\t\t\t\t\t"
|
||||
#define NL "\n\n\n"
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
typedef struct GJSON_ {
|
||||
char *buf; /* pointer to buffer */
|
||||
size_t size; /* size of malloc'd buffer */
|
||||
size_t offset; /* current write offset */
|
||||
} GJSON;
|
||||
|
||||
char *get_json (GHolder * holder, int escape_html);
|
||||
|
||||
void output_json (GHolder * holder, const char *filename);
|
||||
void set_json_nlines (int nl);
|
||||
|
||||
void fpskeyival (FILE * fp, const char *key, int val, int sp, int last);
|
||||
void fpskeysval (FILE * fp, const char *key, const char *val, int sp, int last);
|
||||
void fpskeyaval (FILE * fp, const char *key, const char *val, int sp, int last);
|
||||
|
||||
void fpclose_arr (FILE * fp, int sp, int last);
|
||||
void fpclose_obj (FILE * fp, int iisp, int last);
|
||||
void fpjson (FILE * fp, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
|
||||
void fpopen_arr_attr (FILE * fp, const char *attr, int sp);
|
||||
void fpopen_obj_attr (FILE * fp, const char *attr, int sp);
|
||||
void fpopen_obj (FILE * fp, int iisp);
|
||||
|
||||
#endif
|
||||
+679
@@ -0,0 +1,679 @@
|
||||
/* The MIT License
|
||||
|
||||
Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
An example:
|
||||
|
||||
#include "khash.h"
|
||||
KHASH_MAP_INIT_INT(32, char)
|
||||
int main() {
|
||||
int ret, is_missing;
|
||||
khiter_t k;
|
||||
khash_t(32) *h = kh_init(32);
|
||||
k = kh_put(32, h, 5, &ret);
|
||||
kh_value(h, k) = 10;
|
||||
k = kh_get(32, h, 10);
|
||||
is_missing = (k == kh_end(h));
|
||||
k = kh_get(32, h, 5);
|
||||
kh_del(32, h, k);
|
||||
for (k = kh_begin(h); k != kh_end(h); ++k)
|
||||
if (kh_exist(h, k)) kh_value(h, k) = 1;
|
||||
kh_destroy(32, h);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
2013-05-02 (0.2.8):
|
||||
|
||||
* Use quadratic probing. When the capacity is power of 2, stepping function
|
||||
i*(i+1)/2 guarantees to traverse each bucket. It is better than double
|
||||
hashing on cache performance and is more robust than linear probing.
|
||||
|
||||
In theory, double hashing should be more robust than quadratic probing.
|
||||
However, my implementation is probably not for large hash tables, because
|
||||
the second hash function is closely tied to the first hash function,
|
||||
which reduce the effectiveness of double hashing.
|
||||
|
||||
Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
|
||||
|
||||
2011-12-29 (0.2.7):
|
||||
|
||||
* Minor code clean up; no actual effect.
|
||||
|
||||
2011-09-16 (0.2.6):
|
||||
|
||||
* The capacity is a power of 2. This seems to dramatically improve the
|
||||
speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
|
||||
|
||||
- http://code.google.com/p/ulib/
|
||||
- http://nothings.org/computer/judy/
|
||||
|
||||
* Allow to optionally use linear probing which usually has better
|
||||
performance for random input. Double hashing is still the default as it
|
||||
is more robust to certain non-random input.
|
||||
|
||||
* Added Wang's integer hash function (not used by default). This hash
|
||||
function is more robust to certain non-random input.
|
||||
|
||||
2011-02-14 (0.2.5):
|
||||
|
||||
* Allow to declare global functions.
|
||||
|
||||
2009-09-26 (0.2.4):
|
||||
|
||||
* Improve portability
|
||||
|
||||
2008-09-19 (0.2.3):
|
||||
|
||||
* Corrected the example
|
||||
* Improved interfaces
|
||||
|
||||
2008-09-11 (0.2.2):
|
||||
|
||||
* Improved speed a little in kh_put()
|
||||
|
||||
2008-09-10 (0.2.1):
|
||||
|
||||
* Added kh_clear()
|
||||
* Fixed a compiling error
|
||||
|
||||
2008-09-02 (0.2.0):
|
||||
|
||||
* Changed to token concatenation which increases flexibility.
|
||||
|
||||
2008-08-31 (0.1.2):
|
||||
|
||||
* Fixed a bug in kh_get(), which has not been tested previously.
|
||||
|
||||
2008-08-31 (0.1.1):
|
||||
|
||||
* Added destructor
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __AC_KHASH_H
|
||||
#define __AC_KHASH_H
|
||||
|
||||
/*!
|
||||
@header
|
||||
|
||||
Generic hash table library.
|
||||
*/
|
||||
|
||||
#define AC_VERSION_KHASH_H "0.2.8"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
/* compiler specific configuration */
|
||||
|
||||
#if UINT_MAX == 0xffffffffu
|
||||
typedef unsigned int khint32_t;
|
||||
#elif ULONG_MAX == 0xffffffffu
|
||||
typedef unsigned long khint32_t;
|
||||
#endif
|
||||
|
||||
#if ULONG_MAX == ULLONG_MAX
|
||||
typedef unsigned long khint64_t;
|
||||
#else
|
||||
typedef unsigned long long khint64_t;
|
||||
#endif
|
||||
|
||||
#ifndef kh_inline
|
||||
#ifdef _MSC_VER
|
||||
#define kh_inline __inline
|
||||
#else
|
||||
#define kh_inline inline
|
||||
#endif
|
||||
#endif /* kh_inline */
|
||||
|
||||
#ifndef klib_unused
|
||||
#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3)
|
||||
#define klib_unused __attribute__ ((__unused__))
|
||||
#else
|
||||
#define klib_unused
|
||||
#endif
|
||||
#endif /* klib_unused */
|
||||
|
||||
typedef khint32_t khint_t;
|
||||
typedef khint_t khiter_t;
|
||||
|
||||
#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
|
||||
#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
|
||||
#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
|
||||
#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
|
||||
|
||||
#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
|
||||
|
||||
#ifndef kroundup32
|
||||
#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
|
||||
#endif
|
||||
|
||||
#ifndef kcalloc
|
||||
#define kcalloc(N,Z) calloc(N,Z)
|
||||
#endif
|
||||
#ifndef kmalloc
|
||||
#define kmalloc(Z) malloc(Z)
|
||||
#endif
|
||||
#ifndef krealloc
|
||||
#define krealloc(P,Z) realloc(P,Z)
|
||||
#endif
|
||||
#ifndef kfree
|
||||
#define kfree(P) free(P)
|
||||
#endif
|
||||
|
||||
static const double __ac_HASH_UPPER = 0.77;
|
||||
|
||||
#define __KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
typedef struct kh_##name##_s { \
|
||||
khint_t n_buckets, size, n_occupied, upper_bound; \
|
||||
khint32_t *flags; \
|
||||
khkey_t *keys; \
|
||||
khval_t *vals; \
|
||||
} kh_##name##_t;
|
||||
|
||||
#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
|
||||
extern kh_##name##_t *kh_init_##name(void); \
|
||||
extern void kh_destroy_##name(kh_##name##_t *h); \
|
||||
extern void kh_clear_##name(kh_##name##_t *h); \
|
||||
extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
|
||||
extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
|
||||
extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
|
||||
extern void kh_del_##name(kh_##name##_t *h, khint_t x); \
|
||||
|
||||
#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
SCOPE kh_##name##_t *kh_init_##name(void) { \
|
||||
return (kh_##name##_t*) kcalloc(1, sizeof(kh_##name##_t)); \
|
||||
} \
|
||||
SCOPE void kh_destroy_##name(kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h) { \
|
||||
kfree ((void *) h->keys); \
|
||||
kfree (h->flags); \
|
||||
kfree ((void *) h->vals); \
|
||||
kfree (h); \
|
||||
} \
|
||||
} \
|
||||
SCOPE void kh_clear_##name(kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h && h->flags) { \
|
||||
memset (h->flags, 0xaa, __ac_fsize (h->n_buckets) * sizeof (khint32_t)); \
|
||||
h->size = h->n_occupied = 0; \
|
||||
} \
|
||||
} \
|
||||
SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
|
||||
{ \
|
||||
if (h->n_buckets) { \
|
||||
khint_t k, i, last, mask, step = 0; \
|
||||
mask = h->n_buckets - 1; \
|
||||
k = __hash_func (key); \
|
||||
i = k & mask; \
|
||||
last = i; \
|
||||
while (!__ac_isempty (h->flags, i) && \
|
||||
(__ac_isdel (h->flags, i) || !__hash_equal (h->keys[i], key))) { \
|
||||
i = (i + (++step)) & mask; \
|
||||
if (i == last) \
|
||||
return h->n_buckets; \
|
||||
} \
|
||||
return __ac_iseither (h->flags, i) ? h->n_buckets : i; \
|
||||
} else \
|
||||
return 0; \
|
||||
} \
|
||||
SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
|
||||
{ \
|
||||
/* This function uses 0.25*n_buckets bytes of working space instead of */ \
|
||||
/* [sizeof(key_t+val_t)+.25]*n_buckets. */ \
|
||||
khint32_t *new_flags = 0; \
|
||||
khint_t j = 1; \
|
||||
{ \
|
||||
kroundup32 (new_n_buckets); \
|
||||
if (new_n_buckets < 4) \
|
||||
new_n_buckets = 4; \
|
||||
if (h->size >= (khint_t) (new_n_buckets * __ac_HASH_UPPER + 0.5)) \
|
||||
j = 0; /* requested size is too small */ \
|
||||
else { /* hash table size to be changed (shrink or expand); rehash */ \
|
||||
new_flags = (khint32_t *) kmalloc (__ac_fsize (new_n_buckets) * sizeof (khint32_t)); \
|
||||
if (!new_flags) \
|
||||
return -1; \
|
||||
memset (new_flags, 0xaa, __ac_fsize (new_n_buckets) * sizeof (khint32_t)); \
|
||||
if (h->n_buckets < new_n_buckets) { /* expand */ \
|
||||
khkey_t *new_keys = (khkey_t *) krealloc ((void *) h->keys, new_n_buckets * sizeof (khkey_t)); \
|
||||
if (!new_keys) { \
|
||||
kfree (new_flags); \
|
||||
return -1; \
|
||||
} \
|
||||
h->keys = new_keys; \
|
||||
if (kh_is_map) { \
|
||||
khval_t *new_vals = (khval_t *) krealloc ((void *) h->vals, new_n_buckets * sizeof (khval_t)); \
|
||||
if (!new_vals) { \
|
||||
kfree (new_flags); \
|
||||
return -1; \
|
||||
} \
|
||||
h->vals = new_vals; \
|
||||
} \
|
||||
} /* otherwise shrink */ \
|
||||
} \
|
||||
} \
|
||||
if (j) { /* rehashing is needed */ \
|
||||
for (j = 0; j != h->n_buckets; ++j) { \
|
||||
if (__ac_iseither (h->flags, j) == 0) { \
|
||||
khkey_t key = h->keys[j]; \
|
||||
khval_t val; \
|
||||
khint_t new_mask; \
|
||||
new_mask = new_n_buckets - 1; \
|
||||
if (kh_is_map) \
|
||||
val = h->vals[j]; \
|
||||
__ac_set_isdel_true (h->flags, j); \
|
||||
while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
|
||||
khint_t k, i, step = 0; \
|
||||
k = __hash_func (key); \
|
||||
i = k & new_mask; \
|
||||
while (!__ac_isempty (new_flags, i)) \
|
||||
i = (i + (++step)) & new_mask; \
|
||||
__ac_set_isempty_false (new_flags, i); \
|
||||
if (i < h->n_buckets && __ac_iseither (h->flags, i) == 0) { /* kick out the existing element */ \
|
||||
{ \
|
||||
khkey_t tmp = h->keys[i]; \
|
||||
h->keys[i] = key; \
|
||||
key = tmp; \
|
||||
} \
|
||||
if (kh_is_map) { \
|
||||
khval_t tmp = h->vals[i]; \
|
||||
h->vals[i] = val; \
|
||||
val = tmp; \
|
||||
} \
|
||||
__ac_set_isdel_true (h->flags, i); /* mark it as deleted in the old hash table */ \
|
||||
} else { /* write the element and jump out of the loop */ \
|
||||
h->keys[i] = key; \
|
||||
if (kh_is_map) \
|
||||
h->vals[i] = val; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
|
||||
h->keys = (khkey_t *) krealloc ((void *) h->keys, new_n_buckets * sizeof (khkey_t)); \
|
||||
if (kh_is_map) \
|
||||
h->vals = (khval_t *) krealloc ((void *) h->vals, new_n_buckets * sizeof (khval_t)); \
|
||||
} \
|
||||
kfree (h->flags); /* free the working space */ \
|
||||
h->flags = new_flags; \
|
||||
h->n_buckets = new_n_buckets; \
|
||||
h->n_occupied = h->size; \
|
||||
h->upper_bound = (khint_t) (h->n_buckets * __ac_HASH_UPPER + 0.5); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
|
||||
{ \
|
||||
khint_t x; \
|
||||
if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
|
||||
if (h->n_buckets > (h->size << 1)) { \
|
||||
if (kh_resize_##name (h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
|
||||
*ret = -1; \
|
||||
return h->n_buckets; \
|
||||
} \
|
||||
} else if (kh_resize_##name (h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
|
||||
*ret = -1; \
|
||||
return h->n_buckets; \
|
||||
} \
|
||||
} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
|
||||
{ \
|
||||
khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
|
||||
x = site = h->n_buckets; \
|
||||
k = __hash_func (key); \
|
||||
i = k & mask; \
|
||||
if (__ac_isempty (h->flags, i)) \
|
||||
x = i; /* for speed up */ \
|
||||
else { \
|
||||
last = i; \
|
||||
while (!__ac_isempty (h->flags, i) && (__ac_isdel (h->flags, i) || !__hash_equal (h->keys[i], key))) { \
|
||||
if (__ac_isdel (h->flags, i)) \
|
||||
site = i; \
|
||||
i = (i + (++step)) & mask; \
|
||||
if (i == last) { \
|
||||
x = site; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
if (x == h->n_buckets) { \
|
||||
if (__ac_isempty (h->flags, i) && site != h->n_buckets) \
|
||||
x = site; \
|
||||
else \
|
||||
x = i; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (__ac_isempty (h->flags, x)) { /* not present at all */ \
|
||||
h->keys[x] = key; \
|
||||
__ac_set_isboth_false (h->flags, x); \
|
||||
++h->size; \
|
||||
++h->n_occupied; \
|
||||
*ret = 1; \
|
||||
} else if (__ac_isdel (h->flags, x)) { /* deleted */ \
|
||||
h->keys[x] = key; \
|
||||
__ac_set_isboth_false (h->flags, x); \
|
||||
++h->size; \
|
||||
*ret = 2; \
|
||||
} else \
|
||||
*ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
|
||||
return x; \
|
||||
} \
|
||||
SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
|
||||
{ \
|
||||
if (x != h->n_buckets && !__ac_iseither (h->flags, x)) { \
|
||||
__ac_set_isdel_true (h->flags, x); \
|
||||
--h->size; \
|
||||
} \
|
||||
} \
|
||||
|
||||
#define KHASH_DECLARE(name, khkey_t, khval_t) \
|
||||
__KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
__KHASH_PROTOTYPES(name, khkey_t, khval_t)
|
||||
|
||||
#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
__KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
|
||||
|
||||
#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
|
||||
/* --- BEGIN OF HASH FUNCTIONS --- */
|
||||
|
||||
/*! @function
|
||||
@abstract Integer hash function
|
||||
@param key The integer [khint32_t]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_int_hash_func(key) (khint32_t)(key)
|
||||
/*! @function
|
||||
@abstract Integer comparison function
|
||||
*/
|
||||
#define kh_int_hash_equal(a, b) ((a) == (b))
|
||||
/*! @function
|
||||
@abstract 64-bit integer hash function
|
||||
@param key The integer [khint64_t]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
|
||||
/*! @function
|
||||
@abstract 64-bit integer comparison function
|
||||
*/
|
||||
#define kh_int64_hash_equal(a, b) ((a) == (b))
|
||||
/*! @function
|
||||
@abstract const char* hash function
|
||||
@param s Pointer to a null terminated string
|
||||
@return The hash value
|
||||
*/
|
||||
#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ >= 4)
|
||||
__attribute__((no_sanitize ("unsigned-integer-overflow")))
|
||||
#if (__clang_major__ >= 12)
|
||||
__attribute__((no_sanitize ("unsigned-shift-base")))
|
||||
#endif
|
||||
#endif
|
||||
static kh_inline khint_t
|
||||
__ac_X31_hash_string (const char *s) {
|
||||
khint_t h = (khint_t) * s;
|
||||
if (h)
|
||||
for (++s; *s; ++s)
|
||||
h = (h << 5) - h + (khint_t) * s;
|
||||
return h;
|
||||
}
|
||||
|
||||
/*! @function
|
||||
@abstract Another interface to const char* hash function
|
||||
@param key Pointer to a null terminated string [const char*]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_str_hash_func(key) __ac_X31_hash_string(key)
|
||||
/*! @function
|
||||
@abstract Const char* comparison function
|
||||
*/
|
||||
#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
|
||||
|
||||
static kh_inline khint_t
|
||||
__ac_Wang_hash (khint_t key) {
|
||||
key += ~(key << 15);
|
||||
key ^= (key >> 10);
|
||||
key += (key << 3);
|
||||
key ^= (key >> 6);
|
||||
key += ~(key << 11);
|
||||
key ^= (key >> 16);
|
||||
return key;
|
||||
}
|
||||
|
||||
#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key)
|
||||
|
||||
/* --- END OF HASH FUNCTIONS --- */
|
||||
|
||||
/* Other convenient macros... */
|
||||
|
||||
/*!
|
||||
@abstract Type of the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define khash_t(name) kh_##name##_t
|
||||
|
||||
/*! @function
|
||||
@abstract Initiate a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@return Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_init(name) kh_init_##name()
|
||||
|
||||
/*! @function
|
||||
@abstract Destroy a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_destroy(name, h) kh_destroy_##name(h)
|
||||
|
||||
/*! @function
|
||||
@abstract Reset a hash table without deallocating memory.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_clear(name, h) kh_clear_##name(h)
|
||||
|
||||
/*! @function
|
||||
@abstract Resize a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param s New size [khint_t]
|
||||
*/
|
||||
#define kh_resize(name, h, s) kh_resize_##name(h, s)
|
||||
|
||||
/*! @function
|
||||
@abstract Insert a key to the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Key [type of keys]
|
||||
@param r Extra return code: -1 if the operation failed;
|
||||
0 if the key is present in the hash table;
|
||||
1 if the bucket is empty (never used); 2 if the element in
|
||||
the bucket has been deleted [int*]
|
||||
@return Iterator to the inserted element [khint_t]
|
||||
*/
|
||||
#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
|
||||
|
||||
/*! @function
|
||||
@abstract Retrieve a key from the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Key [type of keys]
|
||||
@return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
|
||||
*/
|
||||
#define kh_get(name, h, k) kh_get_##name(h, k)
|
||||
|
||||
/*! @function
|
||||
@abstract Remove a key from the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Iterator to the element to be deleted [khint_t]
|
||||
*/
|
||||
#define kh_del(name, h, k) kh_del_##name(h, k)
|
||||
|
||||
/*! @function
|
||||
@abstract Test whether a bucket contains data.
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return 1 if containing data; 0 otherwise [int]
|
||||
*/
|
||||
#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
|
||||
|
||||
/*! @function
|
||||
@abstract Get key given an iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return Key [type of keys]
|
||||
*/
|
||||
#define kh_key(h, x) ((h)->keys[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Get value given an iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return Value [type of values]
|
||||
@discussion For hash sets, calling this results in segfault.
|
||||
*/
|
||||
#define kh_val(h, x) ((h)->vals[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Alias of kh_val()
|
||||
*/
|
||||
#define kh_value(h, x) ((h)->vals[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Get the start iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return The start iterator [khint_t]
|
||||
*/
|
||||
#define kh_begin(h) (khint_t)(0)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the end iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return The end iterator [khint_t]
|
||||
*/
|
||||
#define kh_end(h) ((h)->n_buckets)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the number of elements in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return Number of elements in the hash table [khint_t]
|
||||
*/
|
||||
#define kh_size(h) ((h)->size)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the number of buckets in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return Number of buckets in the hash table [khint_t]
|
||||
*/
|
||||
#define kh_n_buckets(h) ((h)->n_buckets)
|
||||
|
||||
/*! @function
|
||||
@abstract Iterate over the entries in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param kvar Variable to which key will be assigned
|
||||
@param vvar Variable to which value will be assigned
|
||||
@param code Block of code to execute
|
||||
*/
|
||||
#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
|
||||
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
|
||||
if (!kh_exist(h,__i)) continue; \
|
||||
(kvar) = kh_key(h,__i); \
|
||||
(vvar) = kh_val(h,__i); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
/*! @function
|
||||
@abstract Iterate over the values in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param vvar Variable to which value will be assigned
|
||||
@param code Block of code to execute
|
||||
*/
|
||||
#define kh_foreach_value(h, vvar, code) { khint_t __i; \
|
||||
for (__i = kh_begin (h); __i != kh_end (h); ++__i) { \
|
||||
if (!kh_exist (h, __i)) \
|
||||
continue; \
|
||||
(vvar) = kh_val (h, __i); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
/* More convenient interfaces */
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash set containing integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_INT(name) \
|
||||
KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_INT(name, khval_t) \
|
||||
KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing 64-bit integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_INT64(name) \
|
||||
KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing 64-bit integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_INT64(name, khval_t) \
|
||||
KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
|
||||
|
||||
typedef const char *kh_cstr_t;
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing const char* keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_STR(name) \
|
||||
KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing const char* keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_STR(name, khval_t) \
|
||||
KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
|
||||
|
||||
#endif /* __AC_KHASH_H */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user