├── .gitignore ├── .sai.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── assets ├── favicon.ico ├── github.css ├── gitohashi-custom.css ├── highlight.pack.js ├── inconsolata.ttf ├── jg2.css ├── jg2.js ├── logo.css ├── showdown.min.js └── showdown.min.js.map ├── cmake └── config.h.in ├── doc ├── README-JSON.md ├── README-build.md ├── README-cache.md ├── README-gitolite.md ├── README-libjsongit2.md └── doc-assets │ ├── deps-goh.svg │ ├── gitohashi-overview.svg │ ├── libjsongit2-acl.svg │ ├── libjsongit2-overview.svg │ ├── libjsongit2-sandwich.svg │ └── naked-and-sandwich.svg ├── etc-gitohashi ├── conf └── conf.d │ ├── localhost │ └── unixskt ├── example-cert ├── localhost-100y.cert.pem └── localhost-100y.key.pem ├── examples ├── minimal │ ├── README.md │ └── jg2-example.c └── threadchurn │ ├── README.md │ └── threadchurn.c ├── include └── libjsongit2.h ├── lgtm.yml ├── lib ├── README.md ├── cache.c ├── conf │ ├── gitolite │ │ ├── common.c │ │ └── gitolite3.c │ ├── private.h │ └── scan-repos.c ├── email │ ├── README.md │ ├── email.c │ ├── md5.c │ └── private.h ├── job │ ├── blame.c │ ├── blog.c │ ├── commit.c │ ├── job.c │ ├── log.c │ ├── no-snapshot.c │ ├── plain.c │ ├── private.h │ ├── reflist.c │ ├── repos.c │ ├── search.c │ ├── snapshot.c │ └── tree.c ├── main.c ├── private.h ├── repostate.c └── util.c ├── src ├── main.c ├── protocol_avatar-proxy.c └── protocol_gitohashi.c ├── system ├── gitohashi-selinux.pp └── gitohashi.service ├── templates └── gitohashi-example.html └── xss ├── README.md └── xss.c /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .cproject 3 | .project 4 | .settings 5 | -------------------------------------------------------------------------------- /.sai.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": "sai-1", 3 | 4 | "platforms": { 5 | "rocky9/aarch64-a72a55-rk3588/gcc": { 6 | "build": "${prep} mkdir build destdir;cd build;export CCACHE_DISABLE=1;export SAI_CPACK=\"-G RPM\";cmake .. ${cmake} && make -j && make -j DESTDIR=../destdir install && ctest -j4 --output-on-failure ${cpack}" 7 | } 8 | }, 9 | "configurations": { 10 | "default": { 11 | "prep": "cd .. && rm -rf libwebsockets && git clone https://libwebsockets.org/repo/libwebsockets && cd libwebsockets && mkdir build && cd build && cmake .. -DLWS_FOR_GITOHASHI=1 && make -j && cd ../../gitohashi &&", 12 | "cmake": "-DGOH_LWS_INC_PATH=../../libwebsockets/build/include -DGOH_LWS_LIB_PATH=../../libwebsockets/build/lib/libwebsockets.so", 13 | "cpack": "&& cpack $SAI_CPACK", 14 | "artifacts": "build/*.rpm, build/*.deb, build/*.zip" 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | include(CheckCSourceCompiles) 3 | include(CheckIncludeFile) 4 | 5 | set(PROJ gitohashi) 6 | project (gitohashi C) 7 | 8 | # 9 | # You can override where gitohashi gets its dependent library and includes 10 | # from, at the cmake commandline. For libwebsockets, eg: 11 | # 12 | # cmake .. -DGOH_LWS_INC_PATH=/usr/local/include \ 13 | # -DGOH_LWS_LIB_PATH=/usr/local/lib/libwebsockets.so 14 | # 15 | # For git2, eg 16 | # 17 | # cmake .. -DJG2_GIT2_INC_PATH=/usr/local/include \ 18 | # -DJG2_GIT2_LIB_PATH=/usr/local/lib/libgit2.so 19 | # 20 | # and for libarchive, eg 21 | # 22 | # cmake .. -DJG2_ARCHIVE_INC_PATH=/usr/local/include \ 23 | # 24 | 25 | # enable this if you want to run gitohashi from another lws application via 26 | # its protocol plugins alone 27 | # 28 | option(GOH_LWS_PLUGINS "Also build lws protocol plugins" OFF) 29 | option(GOH_WITH_ASAN "Build with libasan" OFF) 30 | 31 | 32 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Gitohashi") 33 | set(CPACK_PACKAGE_VENDOR "Andy Green ") 34 | set(CPACK_PACKAGE_DESCRIPTION "Advanced gitweb based on libwebsockets") 35 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 36 | set(CPACK_PACKAGE_VERSION_MAJOR "0") 37 | set(CPACK_PACKAGE_VERSION_MINOR "2") 38 | set(CPACK_PACKAGE_VERSION_PATCH "99") 39 | 40 | 41 | set(GOH_DEPLIBS jsongit2) 42 | set(JG2_DEPLIBS pthread) 43 | 44 | if (NOT GOH_LWS_LIB_PATH) 45 | set(GOH_DEPLIB ${GOH_DEPLIBS} websockets) 46 | set(JG2_DEPLIBS ${JG2_DEPLIBS} websockets) 47 | endif() 48 | 49 | # 50 | # libgit2 paths 51 | # 52 | find_path( JG2_GIT2_INC_PATH NAMES "git2.h") 53 | find_library(JG2_GIT2_LIB_PATH NAMES "git2") 54 | 55 | if (JG2_GIT2_INC_PATH AND JG2_GIT2_LIB_PATH) 56 | set(JG2_DEPLIBS ${JG2_GIT2_LIB_PATH} ${JG2_DEPLIBS}) 57 | include_directories(BEFORE "${JG2_GIT2_INC_PATH}") 58 | else() 59 | message(FATAL_ERROR " Unable to find libgit2") 60 | endif() 61 | 62 | # 63 | # libarchive paths 64 | # 65 | find_path( JG2_ARCHIVE_INC_PATH NAMES "archive.h") 66 | find_library(JG2_ARCHIVE_LIB_PATH NAMES "archive") 67 | 68 | if (JG2_ARCHIVE_INC_PATH AND JG2_ARCHIVE_LIB_PATH) 69 | set(JG2_DEPLIBS ${JG2_ARCHIVE_LIB_PATH} ${JG2_DEPLIBS}) 70 | include_directories(BEFORE "${JG2_ARCHIVE_INC_PATH}") 71 | set(JG2_HAVE_ARCHIVE_H "Y") 72 | endif() 73 | 74 | find_package(Git) 75 | if(GIT_EXECUTABLE) 76 | execute_process( 77 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 78 | COMMAND "${GIT_EXECUTABLE}" describe --tags --always 79 | OUTPUT_VARIABLE GIT_HASH 80 | OUTPUT_STRIP_TRAILING_WHITESPACE 81 | ) 82 | 83 | set(LWS_BUILD_HASH ${GIT_HASH}) 84 | 85 | # append the build user and hostname 86 | if (NOT LWS_REPRODUCIBLE) 87 | execute_process( 88 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 89 | COMMAND "whoami" 90 | OUTPUT_VARIABLE GIT_USER 91 | OUTPUT_STRIP_TRAILING_WHITESPACE 92 | ) 93 | execute_process( 94 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 95 | COMMAND "hostname" 96 | OUTPUT_VARIABLE GIT_HOST 97 | OUTPUT_STRIP_TRAILING_WHITESPACE 98 | ) 99 | string(REGEX REPLACE "([^\\])[\\]([^\\])" "\\1\\\\\\\\\\2" GIT_USER ${GIT_USER}) 100 | set(LWS_BUILD_HASH ${GIT_USER}@${GIT_HOST}-${GIT_HASH}) 101 | endif() 102 | 103 | message("Git commit hash: ${LWS_BUILD_HASH}") 104 | endif() 105 | 106 | set(CPACK_PACKAGE_VERSION_PATCH "${CPACK_PACKAGE_VERSION_PATCH_NUMBER}-${LWS_BUILD_HASH}") 107 | 108 | 109 | # 110 | # libwebsockets paths 111 | # 112 | find_path( GOH_LWS_INC_PATH NAMES "libwebsockets.h") 113 | find_library(GOH_LWS_LIB_PATH NAMES "websockets") 114 | 115 | if (GOH_LWS_INC_PATH AND GOH_LWS_LIB_PATH) 116 | set(GOH_DEPLIBS ${GOH_LWS_LIB_PATH} ${GOH_DEPLIBS}) 117 | include_directories(BEFORE "${GOH_LWS_INC_PATH}") 118 | else() 119 | message(FATAL_ERROR " Unable to find libwebsockets") 120 | endif() 121 | 122 | unset(ASAN_LIBS) 123 | if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) 124 | include (CheckCCompilerFlag) 125 | 126 | if (GOH_WITH_ASAN) 127 | set(ASAN_FLAGS "-fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak -fsanitize=undefined -fsanitize-address-use-after-scope -fsanitize-undefined-trap-on-error") 128 | set(ASAN_LIBS "asan") 129 | message("Enabling ASAN") 130 | endif() 131 | 132 | set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wignored-qualifiers -Wtype-limits -Wuninitialized -Werror -Wundef ${CMAKE_C_FLAGS} ${ASAN_FLAGS}" ) 133 | endif() 134 | 135 | set(CMAKE_REQUIRED_INCLUDES "include") 136 | CHECK_C_SOURCE_COMPILES( 137 | "#include 138 | int main(int argc, char **argv) { return GIT_BLAME_USE_MAILMAP; } 139 | " JG2_HAVE_BLAME_MAILMAP) 140 | 141 | set(CMAKE_REQUIRED_FLAGS "-pthread") 142 | CHECK_C_SOURCE_COMPILES("#define _GNU_SOURCE 143 | #include 144 | void *dummy(void *arg) { pthread_exit(NULL); return NULL; } 145 | int main(int argc, char *argv[]) { 146 | pthread_t th; 147 | 148 | if (pthread_create(&th, NULL, dummy, NULL)) 149 | pthread_setname_np(th, \"test\"); 150 | 151 | return 0; 152 | }" JG2_HAS_PTHREAD_SETNAME_NP) 153 | 154 | set(JG2_SOURCES lib/cache.c 155 | lib/main.c 156 | lib/repostate.c 157 | lib/util.c 158 | 159 | lib/job/job.c 160 | lib/job/reflist.c 161 | lib/job/log.c 162 | lib/job/commit.c 163 | lib/job/tree.c 164 | lib/job/plain.c 165 | lib/job/repos.c 166 | lib/job/blame.c 167 | lib/job/blog.c 168 | lib/job/search.c 169 | 170 | lib/conf/gitolite/gitolite3.c 171 | lib/conf/gitolite/common.c 172 | lib/conf/scan-repos.c 173 | 174 | lib/email/md5.c 175 | lib/email/email.c 176 | ) 177 | 178 | if (JG2_HAVE_ARCHIVE_H) 179 | set(JG2_SOURCES ${JG2_SOURCES} lib/job/snapshot.c) 180 | set(JG2_DEPLIBS ${JG2_DEPLIBS} archive) 181 | else() 182 | set(JG2_SOURCES ${JG2_SOURCES} lib/job/no-snapshot.c) 183 | endif() 184 | 185 | configure_file("cmake/config.h.in" "${PROJECT_BINARY_DIR}/jg2-config.h") 186 | add_library(jsongit2 SHARED ${JG2_SOURCES}) 187 | set(HDR_PUBLIC "include/libjsongit2.h" "${PROJECT_BINARY_DIR}/jg2-config.h") 188 | set_target_properties(jsongit2 PROPERTIES PUBLIC_HEADER "${HDR_PUBLIC}") 189 | target_include_directories(jsongit2 PRIVATE "${PROJECT_BINARY_DIR}" 190 | "${PROJECT_SOURCE_DIR}/include") 191 | target_link_libraries(jsongit2 ${ASAN_LIBS} ${GOH_LWS_LIB_PATH} ${JG2_DEPLIBS}) 192 | 193 | set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") 194 | set(CPACK_COMPONENT_DEV_DISPLAY_NAME "Development files") 195 | 196 | # jsongit2 examples 197 | 198 | add_executable(jg2-example examples/minimal/jg2-example.c) 199 | target_link_libraries(jg2-example ${ASAN_LIBS} ${GOH_LWS_LIB_PATH} jsongit2) 200 | target_include_directories(jg2-example PRIVATE "${PROJECT_SOURCE_DIR}/include") 201 | 202 | add_executable(jg2-threadchurn examples/threadchurn/threadchurn.c) 203 | target_link_libraries(jg2-threadchurn ${ASAN_LIBS} ${GOH_LWS_LIB_PATH} jsongit2 pthread) 204 | target_include_directories(jg2-threadchurn PRIVATE "${PROJECT_SOURCE_DIR}/include") 205 | 206 | 207 | message("----------------------------- dependent libs -----------------------------") 208 | message(" libgit2: include: ${JG2_GIT2_INC_PATH}, lib: ${JG2_GIT2_LIB_PATH}") 209 | message(" libgit2: has GIT_BLAME_USE_MAILMAP: ${JG2_HAVE_BLAME_MAILMAP}") 210 | if (NOT JG2_HAVE_ARCHIVE_H) 211 | message(" libarchive: not found") 212 | else() 213 | message(" libarchive: include: ${JG2_ARCHIVE_INC_PATH}, lib: ${JG2_ARCHIVE_LIB_PATH}") 214 | endif() 215 | message(" libwebsockets: include: ${GOH_LWS_INC_PATH}, lib: ${GOH_LWS_LIB_PATH}") 216 | message(" lws plugins: ${GOH_LWS_PLUGINS}") 217 | 218 | set(LIB_DIR lib CACHE PATH "Install dir for libraries") 219 | set(BIN_DIR bin CACHE PATH "Install dir for executables") 220 | set(INCLUDE_DIR include CACHE PATH "Install dir for header files") 221 | set(DATA_DIR share CACHE PATH "Install dir for data files") 222 | 223 | install(TARGETS jsongit2 224 | jg2-example 225 | jg2-threadchurn 226 | 227 | LIBRARY DESTINATION "${LIB_DIR}${LIB_SUFFIX}" COMPONENT libraries 228 | ARCHIVE DESTINATION "${LIB_DIR}${LIB_SUFFIX}" COMPONENT libraries 229 | RUNTIME DESTINATION "${BIN_DIR}" COMPONENT libraries 230 | PUBLIC_HEADER DESTINATION "${INCLUDE_DIR}" COMPONENT dev) 231 | 232 | 233 | 234 | install(FILES assets/github.css 235 | assets/logo.css 236 | assets/gitohashi-custom.css 237 | assets/highlight.pack.js 238 | assets/jg2.css 239 | assets/jg2.js 240 | assets/favicon.ico 241 | assets/inconsolata.ttf 242 | assets/showdown.min.js 243 | assets/showdown.min.js.map 244 | DESTINATION "${DATA_DIR}/gitohashi/assets") 245 | 246 | install(FILES example-cert/localhost-100y.cert.pem 247 | example-cert/localhost-100y.key.pem 248 | DESTINATION "${DATA_DIR}/gitohashi/example-cert") 249 | 250 | install(FILES templates/gitohashi-example.html 251 | DESTINATION "${DATA_DIR}/gitohashi/templates") 252 | 253 | install(FILES system/gitohashi-selinux.pp 254 | DESTINATION "${DATA_DIR}/gitohashi") 255 | 256 | install(FILES system/gitohashi.service 257 | DESTINATION "${LIB_DIR}/systemd/system") 258 | 259 | add_executable(${PROJ} 260 | src/main.c 261 | ) 262 | 263 | target_link_libraries(${PROJ} ${ASAN_LIBS} ${GOH_LWS_LIB_PATH} jsongit2) 264 | target_include_directories(${PROJ} PRIVATE "${PROJECT_SOURCE_DIR}/include") 265 | 266 | install(TARGETS ${PROJ} RUNTIME DESTINATION ${BIN_DIR}) 267 | 268 | 269 | if (GOH_LWS_PLUGINS) 270 | add_library(protocol_gitohashi SHARED src/protocol_gitohashi.c) 271 | 272 | target_link_libraries(protocol_gitohashi ${ASAN_LIBS} websockets jsongit2) 273 | 274 | set_property(TARGET protocol_gitohashi 275 | PROPERTY COMPILE_DEFINITIONS 276 | INSTALL_DATADIR="${CMAKE_INSTALL_PREFIX}/plugins" 277 | ) 278 | 279 | list(APPEND PLUGINS_LIST protocol_gitohashi) 280 | 281 | 282 | add_library(protocol_avatar_proxy SHARED src/protocol_avatar-proxy.c) 283 | 284 | target_link_libraries(protocol_avatar_proxy ${ASAN_LIBS} websockets jsongit2) 285 | 286 | set_property(TARGET protocol_avatar_proxy 287 | PROPERTY COMPILE_DEFINITIONS 288 | INSTALL_DATADIR="${CMAKE_INSTALL_PREFIX}/plugins" 289 | ) 290 | 291 | list(APPEND PLUGINS_LIST protocol_avatar_proxy) 292 | 293 | install(TARGETS ${PLUGINS_LIST} 294 | PERMISSIONS OWNER_WRITE OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_READ GROUP_READ WORLD_READ 295 | DESTINATION share/libwebsockets-test-server/plugins 296 | COMPONENT plugins) 297 | 298 | endif() 299 | 300 | include(CPack) 301 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warmcat/gitohashi/14d9f49d7480a8bc882284f0c0cdfa6815b6a25d/assets/favicon.ico -------------------------------------------------------------------------------- /assets/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: rgba(248, 248, 248, 0.5); 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #998; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-subst { 24 | color: #333; 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-template-variable, 29 | .hljs-tag .hljs-attr { 30 | color: #008080; 31 | } 32 | 33 | .hljs-number { 34 | color: #800; 35 | } 36 | 37 | .hljs-variable { 38 | color: #008; 39 | } 40 | 41 | .hljs-literal { 42 | color: #080; 43 | } 44 | 45 | .hljs-literal { 46 | color: #080; 47 | } 48 | 49 | .hljs-string, 50 | .hljs-doctag { 51 | color: #d14; 52 | } 53 | 54 | .hljs-title, 55 | .hljs-section, 56 | .hljs-selector-id { 57 | color: #900; 58 | font-weight: bold; 59 | } 60 | 61 | .hljs-subst { 62 | font-weight: normal; 63 | } 64 | 65 | .hljs-type, 66 | .hljs-class .hljs-title { 67 | color: #458; 68 | font-weight: bold; 69 | } 70 | 71 | .hljs-tag, 72 | .hljs-name, 73 | .hljs-attribute { 74 | color: #000080; 75 | font-weight: normal; 76 | } 77 | 78 | .hljs-regexp, 79 | .hljs-link { 80 | color: #009926; 81 | } 82 | 83 | .hljs-symbol, 84 | .hljs-bullet { 85 | color: #990073; 86 | } 87 | 88 | .hljs-built_in, 89 | .hljs-builtin-name { 90 | color: #0086b3; 91 | } 92 | 93 | .hljs-meta { 94 | color: #999; 95 | font-weight: bold; 96 | } 97 | 98 | .hljs-deletion { 99 | background: #fdd; 100 | } 101 | 102 | .hljs-addition { 103 | background: #dfd; 104 | } 105 | 106 | .hljs-emphasis { 107 | font-style: italic; 108 | } 109 | 110 | .hljs-strong { 111 | font-weight: bold; 112 | } 113 | -------------------------------------------------------------------------------- /assets/gitohashi-custom.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: 'Inconsolata'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Inconsolata Regular'), url('/git/_gitohashi/inconsolata.ttf') format('truetype'); 7 | } 8 | body { 9 | font-family: "Open Sans", Roboto, Arial, Helvetica, sans-serif; 10 | font-size: 15px; 11 | padding-bottom:0; 12 | padding-top:0; 13 | margin-bottom:0; 14 | margin-top:0; 15 | } 16 | p { 17 | font-family: "Open Sans", Roboto, Arial, Helvetica, sans-serif; 18 | font-size: 15px; 19 | } 20 | pre { 21 | font-family: "Inconsolata", "mono"; 22 | font-size: 14px; 23 | } 24 | 25 | tr.repobar { 26 | background-color: #dcf4dc; 27 | } 28 | 29 | div.tabs li.selected { 30 | background: #dcf4dc; 31 | color: #333; 32 | } 33 | 34 | div.tabs li { 35 | background: #c0d8c0; 36 | } 37 | 38 | div.tabs li:hover { 39 | background: #cce8cc; 40 | } 41 | 42 | div.tabs a { 43 | color: #00c; 44 | } 45 | -------------------------------------------------------------------------------- /assets/inconsolata.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warmcat/gitohashi/14d9f49d7480a8bc882284f0c0cdfa6815b6a25d/assets/inconsolata.ttf -------------------------------------------------------------------------------- /assets/logo.css: -------------------------------------------------------------------------------- 1 | img.gitohashi-logo { 2 | display: inline-block; 3 | float:left; 4 | background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA2NC4xMjU0NDMgNDEuOTYwMjU4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGRlZnM+PHJhZGlhbEdyYWRpZW50IGlkPSJhIiBjeD0iMjg5LjciIGN5PSI0MTIuOCIgcj0iLjA1Mzc0IiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEuMTczIDAgMCAxLjEwNiAtNTAuMjggLTQzLjY2KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHhsaW5rOmhyZWY9IiNkIi8+PGxpbmVhckdyYWRpZW50IGlkPSJkIj48c3RvcCBzdG9wLWNvbG9yPSIjYzJjOWNhIiBzdG9wLW9wYWNpdHk9Ii4zNzYiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iMCIgb2Zmc2V0PSIxIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImIiIHgxPSI4LjQ3MiIgeDI9IjE3LjM2IiB5MT0iMTI0LjkiIHkyPSIxMjMuOCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIHhsaW5rOmhyZWY9IiNjIi8+PGxpbmVhckdyYWRpZW50IGlkPSJjIj48c3RvcCBzdG9wLWNvbG9yPSIjYzhjNmNhIiBzdG9wLW9wYWNpdHk9IjAiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNjOGM2Y2EiIHN0b3Atb3BhY2l0eT0iLjcyMTYiIG9mZnNldD0iLjM5NTkiLz48c3RvcCBzdG9wLWNvbG9yPSIjYzhjNmNhIiBzdG9wLW9wYWNpdHk9Ii40MTk2IiBvZmZzZXQ9Ii40ODE2Ii8+PHN0b3Agc3RvcC1jb2xvcj0iI2M4YzZjYSIgc3RvcC1vcGFjaXR5PSIwIiBvZmZzZXQ9IjEiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48bWV0YWRhdGE+PHJkZjpSREY+PGNjOldvcmsgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiLz48ZGM6dGl0bGUvPjwvY2M6V29yaz48L3JkZjpSREY+PC9tZXRhZGF0YT48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTE1LjQgLTIwNS4zKSI+PGcgZmlsbD0iIzgwMDAwMCIgc3Ryb2tlLXdpZHRoPSIzLjE4MiIgc3R5bGU9ImZvbnQtZmVhdHVyZS1zZXR0aW5nczpub3JtYWw7Zm9udC12YXJpYW50LWNhcHM6bm9ybWFsO2ZvbnQtdmFyaWFudC1saWdhdHVyZXM6bm9ybWFsO2ZvbnQtdmFyaWFudC1udW1lcmljOm5vcm1hbCIgYXJpYS1sYWJlbD0ib2hhc2hpIj48cGF0aCBkPSJtMTI2LjQgMjQwLjFxMCAzLjQwNi0xLjQ0MiA1LjMwOC0xLjQ0MiAxLjg4OS0zLjk1MyAxLjg4OS0xLjU5MSAwLTIuODA5LTAuODcwMS0xLjIxOC0wLjg3MDEtMS44NzctMi40OTgtMC42NTg4LTEuNjQxLTAuNjU4OC0zLjgyOCAwLTMuMzY5IDEuNDI5LTUuMjU4IDEuNDI5LTEuOTAyIDMuOTY1LTEuOTAyIDIuNDQ5IDAgMy44OTEgMS45MzkgMS40NTQgMS45MjcgMS40NTQgNS4yMjF6bS03LjM1OSAwcTAgMi4xNjMgMC40NzIzIDMuMjY5dDEuNTE2IDEuMTA2cTEuOTg5IDAgMS45ODktNC4zNzUgMC0yLjE3NS0wLjQ4NDgtMy4yNDQtMC40NzIzLTEuMDgxLTEuNTA0LTEuMDgxLTEuMDQ0IDAtMS41MTYgMS4wODEtMC40NzIzIDEuMDY5LTAuNDcyMyAzLjI0NHoiLz48cGF0aCBkPSJtMTM1LjYgMjQ3di04LjI1NHEwLTMuMDQ1LTEuNTI5LTMuMDQ1LTEuMTQ0IDAtMS42MDMgMS4yMDYtMC40NTk5IDEuMTkzLTAuNDU5OSAzLjQ0M3Y2LjY1aC0zLjMwNnYtMTkuMzRoMy4zMDZ2NC4zMDFxMCAwLjgzMjgtMC4xNDkyIDIuOTcxaDAuMTc0cTAuOTk0NC0yLjAzOSAzLjA1OC0yLjAzOSAxLjkwMiAwIDIuODcxIDEuMjkzIDAuOTY5NSAxLjI4IDAuOTY5NSAzLjc3OXY5LjAzN3oiLz48cGF0aCBkPSJtMTQ4LjUgMjQ3LTAuNjU4OC0xLjg4OWgtMC4wNzQ2cS0wLjczMzQgMS4yMTgtMS41MTYgMS42NzgtMC43ODMxIDAuNDU5OS0xLjk3NiAwLjQ1OTktMS41MjkgMC0yLjQxMS0xLjE0NC0wLjg3MDEtMS4xNDQtMC44NzAxLTMuMTgyIDAtMi4xODggMS4xOTMtMy4yNDQgMS4yMDYtMS4wNjkgMy41OTItMS4xODFsMS44NC0wLjA3NDZ2LTAuOTk0NHEwLTEuOTM5LTEuNjc4LTEuOTM5LTEuMjQzIDAtMi44NTkgMC45Njk1bC0xLjE0NC0yLjI4N3EyLjA2My0xLjMwNSA0LjQ4Ny0xLjMwNSAyLjIgMCAzLjM0NCAxLjI2OCAxLjE1NiAxLjI1NSAxLjE1NiAzLjU2N3Y5LjI5OHptLTIuODcxLTIuMzM3cTAuOTA3NCAwIDEuNDQyLTAuODA4IDAuNTQ2OS0wLjgyMDQgMC41NDY5LTIuMTc1di0xLjE2OGwtMS4wMTkgMC4wNDk3cS0xLjEzMSAwLjA2MjItMS42NjYgMC42NDY0LTAuNTIyMSAwLjU4NDItMC41MjIxIDEuNzQgMCAxLjcxNSAxLjIxOCAxLjcxNXoiLz48cGF0aCBkPSJtMTYxIDI0Mi45cTAgMi4wMjYtMS4xOTMgMy4xNy0xLjE4MSAxLjEzMS0zLjQ5MyAxLjEzMS0yLjA1MSAwLTMuMzkzLTAuODA4di0yLjg3MXEwLjY4MzcgMC40NzIzIDEuNTkxIDAuNzgzMSAwLjkxOTggMC4zMTA4IDEuNjE2IDAuMzEwOCAwLjc3MDcgMCAxLjE2OC0wLjM3MjkgMC40MTAyLTAuMzg1MyAwLjQxMDItMS4wMDcgMC0wLjYzMzktMC40MjI2LTEuMTA2dC0xLjU1NC0xLjE1NnEtMS41MDQtMC44OTUtMi4xNjMtMS45MDItMC42NDY0LTEuMDE5LTAuNjQ2NC0yLjM5OSAwLTEuNzY1IDEuMjA2LTIuNzk3IDEuMjE4LTEuMDMyIDMuMzMxLTEuMDMyIDEuODQgMCAzLjQ2OCAwLjk2OTZsLTEuMDgxIDIuMzc0cS0xLjMzLTAuNzIwOS0yLjMzNy0wLjcyMDktMC42MzM5IDAtMC45NTcxIDAuMzM1Ni0wLjMxMDggMC4zMzU2LTAuMzEwOCAwLjgyMDQgMCAwLjU1OTQgMC4zODUzIDAuOTk0NCAwLjM5NzggMC40MzUgMS40NjcgMS4wODEgMS4yOTMgMC43ODMxIDEuODE1IDEuMzMgMC41MjIxIDAuNTQ2OSAwLjgwOCAxLjI0MyAwLjI4NTkgMC42OTYxIDAuMjg1OSAxLjYyOHoiLz48cGF0aCBkPSJtMTcwIDI0N3YtOC4yNTRxMC0zLjA0NS0xLjUyOS0zLjA0NS0xLjE0NCAwLTEuNjAzIDEuMjA2LTAuNDU5OSAxLjE5My0wLjQ1OTkgMy40NDN2Ni42NWgtMy4zMDZ2LTE5LjM0aDMuMzA2djQuMzAxcTAgMC44MzI4LTAuMTQ5MiAyLjk3MWgwLjE3NHEwLjk5NDQtMi4wMzkgMy4wNTgtMi4wMzkgMS45MDIgMCAyLjg3MSAxLjI5MyAwLjk2OTYgMS4yOCAwLjk2OTYgMy43Nzl2OS4wMzd6Ii8+PHBhdGggZD0ibTE3OS40IDI0N2gtMy4zMDZ2LTEzLjg1aDMuMzA2em0tMy40NDMtMTcuNDZxMC0wLjgyMDQgMC40NzIzLTEuMjkzIDAuNDg0OC0wLjQ4NDggMS4zNDItMC40ODQ4IDAuODA4IDAgMS4yOTMgMC40ODQ4IDAuNDg0OCAwLjQ3MjQgMC40ODQ4IDEuMjkzIDAgMC44NDUyLTAuNDk3MiAxLjMxOC0wLjQ5NzIgMC40NTk5LTEuMjggMC40NTk5LTAuODIwNCAwLTEuMzE4LTAuNDU5OS0wLjQ5NzItMC40NzIzLTAuNDk3Mi0xLjMxOHoiLz48L2c+PGcgc3Ryb2tlLXdpZHRoPSIzLjI1MiIgc3R5bGU9ImZvbnQtZmVhdHVyZS1zZXR0aW5nczpub3JtYWw7Zm9udC12YXJpYW50LWNhcHM6bm9ybWFsO2ZvbnQtdmFyaWFudC1saWdhdHVyZXM6bm9ybWFsO2ZvbnQtdmFyaWFudC1udW1lcmljOm5vcm1hbCIgYXJpYS1sYWJlbD0iZ2l0Ij48cGF0aCBkPSJtMTI2LjggMjEwLjh2MS45ODJsLTEuNjg5IDAuMzgxMXEwLjQ5NTQgMC44ODkyIDAuNDk1NCAyLjIzNiAwIDIuMjQ4LTEuMjE5IDMuNTE5dC0zLjQwNCAxLjI3cS0wLjYzNTEgMC0wLjk0LTAuMTAxNi0wLjI0MTQgMC4yMTU5LTAuNDU3MyAwLjQ4MjctMC4yMTU5IDAuMjY2OC0wLjIxNTkgMC42NjA1IDAgMC44ODkyIDEuNTM3IDAuODg5MmgyLjA0NXExLjgwNCAwIDIuNzY5IDAuOTc4MSAwLjk3ODEgMC45NjU0IDAuOTc4MSAyLjkyMiAwIDIuNDY0LTEuNjUxIDMuODExLTEuNjUxIDEuMzQ2LTQuNzUxIDEuMzQ2LTIuMzUgMC0zLjYyLTEuMDI5LTEuMjctMS4wMTYtMS4yNy0yLjkwOSAwLTEuMTgxIDAuNzI0LTIuMDgzIDAuNzI0LTAuODg5MiAyLjAwNy0xLjI5Ni0wLjY0NzgtMC4yNTQtMS4wNjctMC43ODc1LTAuNDA2NS0wLjUzMzUtMC40MDY1LTEuMjQ1IDAtMC40MzE5IDAuMTI3LTAuNzc0OCAwLjEyNy0wLjM1NTcgMC4zNTU3LTAuNjYwNSAwLjI0MTMtMC4zMTc2IDAuOTY1NC0wLjkyNzMtMC44NzY0LTAuNTMzNS0xLjM4NS0xLjYzOS0wLjUwODEtMS4xMDUtMC41MDgxLTIuNDUyIDAtMi4zMjQgMS4yMTktMy41OTV0My40OTMtMS4yN3EwLjQzMTkgMCAwLjk5MDggMC4xMDE2IDAuNTU4OSAwLjA4ODkgMC44NTEgMC4xOTA1em0tOC4zMiAxNi4xOHEwIDAuODM4NCAwLjUyMDggMS4yODMgMC41MzM1IDAuNDU3MyAxLjUxMiAwLjQ1NzMgMS41MzcgMCAyLjI5OS0wLjU4NDMgMC43NzQ4LTAuNTg0MyAwLjc3NDgtMS42IDAtMC44MDAyLTAuNDE5Mi0xLjEzLTAuNDA2NS0wLjMzMDMtMS41MzctMC4zMzAzaC0xLjYzOXEtMC42ODU5IDAtMS4xMDUgMC41MDgxLTAuNDA2NSAwLjUyMDgtMC40MDY1IDEuMzk3em0xLjA0Mi0xMS41OHEwIDIuNTAyIDEuNDEgMi41MDIgMS4zNzIgMCAxLjM3Mi0yLjUyOCAwLTIuNTkxLTEuMzcyLTIuNTkxLTEuNDEgMC0xLjQxIDIuNjE3eiIvPjxwYXRoIGQ9Im0xMzEuOSAyMjVoLTMuMzc5di0xNC4xNWgzLjM3OXptLTMuNTE5LTE3Ljg1cTAtMC44Mzg0IDAuNDgyNy0xLjMyMSAwLjQ5NTQtMC40OTU0IDEuMzcyLTAuNDk1NCAwLjgyNTYgMCAxLjMyMSAwLjQ5NTQgMC40OTU0IDAuNDgyNyAwLjQ5NTQgMS4zMjEgMCAwLjg2MzgtMC41MDgxIDEuMzQ2LTAuNTA4MSAwLjQ3LTEuMzA4IDAuNDctMC44MzgzIDAtMS4zNDYtMC40Ny0wLjUwODEtMC40ODI3LTAuNTA4MS0xLjM0NnoiLz48cGF0aCBkPSJtMTM5LjcgMjIyLjNxMC43MTEzIDAgMS41MzctMC4zNTU3djIuNjQycS0wLjgyNTYgMC41NzE2LTIuNDc3IDAuNTcxNi0xLjg0MiAwLTIuNzMxLTEuMDkyLTAuODg5Mi0xLjEwNS0wLjg4OTItMy4zMDN2LTcuMzI5aC0xLjM3MnYtMS41MTJsMS43NjYtMS4yMTkgMC44NjM4LTIuOTk4aDIuMTU5djMuMDQ5aDIuNTY2djIuNjhoLTIuNTY2djcuNDQzcTAgMS40MjMgMS4xNDMgMS40MjN6Ii8+PC9nPjxnIHRyYW5zZm9ybT0ibWF0cml4KDQuNDg2IDAgMCA0LjQ4NiA5OS4wMyAtMzMyLjYpIj48ZyB0cmFuc2Zvcm09Im1hdHJpeCgxLjkyIC43NTg5IC0uNzU4OSAxLjkyIC0yMzQuNyAtODg3LjUpIj48ZyB0cmFuc2Zvcm09InJvdGF0ZSgtOS4xOTYgMjkwLjUgNDEyLjIpIiBmaWxsPSIjNGQ0ZDRkIj48cGF0aCBkPSJtMjg5LjYgNDEyLjYgNC4xOS0xLjQ0IDAuMDIwOS02ZS0zczAuMDEyNi03LjhlLTQgMC4wMTgzIDguNmUtNGMwLjAyMjMgOWUtMyAwLjAxODUgMC4wMjA4IDAuMDE4MiAwLjAyNzItNGUtMyAwLjAxODEtMC4wMTQzIDAuMDIxNy0wLjAxNDMgMC4wMjE3bC00LjE2OSAxLjU5MS0wLjAxNDYtMC4wNzItMC4wMTQzLTAuMDQzMnoiLz48cGF0aCBkPSJtMjg5LjYgNDEyLjZjLTAuMDUyNCAwLjAyMjYtMC4wODYxIDAuMDg5OS0wLjA2MjcgMC4xNDM3IDAuMDMwNyAwLjA0NjkgMC4wNzg4IDAuMDYxNCAwLjEyNTQgMC4wNTExLTJlLTMgLTAuMDU2Ni0wLjAxMzYtMC4wOTQ5LTAuMDM1My0wLjE1MjItNWUtMyAtMC4wMS0wLjAyMzktMC4wMzQyLTAuMDI5NS0wLjA0MzZ6Ii8+PC9nPjxlbGxpcHNlIGN4PSIyODkuNyIgY3k9IjQxMi44IiByeD0iLjA2MzA3IiByeT0iLjA1OTQzIiBmaWxsPSJ1cmwoI2EpIi8+PC9nPjxwYXRoIGQ9Im04LjE0NCAxMjQuOSAwLjA3Ni0wLjA1ODUgMC4wNzAxNi0wLjAzNTEgOS4xMjEtMS4wNTIgMC4wNDY3NyAwLjAxMTctOC45ODYgMS4xNjMtMC4wNDA5My0wLjA0NjgtMC4wOTkzOS0wLjA0NjgtMC4wOTM1NCAwLjAxMTd6IiBmaWxsPSJ1cmwoI2IpIi8+PHBhdGggZD0ibTguMjM4IDEyNS4yIDAuMTQ2Mi0wLjA2NDMgMC4wNDY3Ny0wLjA0NjggOS4wMzktMS4yNjMtMC4wMzUwOCAwLjAzNTEtOS4wMDkgMS4zNDUtMC4xMjI4IDAuMDExN3oiIGZpbGw9IiM0MjQ4NGMiIGZpbGwtb3BhY2l0eT0iLjcyMTYiLz48dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSgtNy4wOTcpIiB4PSItNi42Mjk1NjYyIiB5PSIxMjUuMTUzNSIgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9IidPcGVuIFNhbnMnIiBmb250LXNpemU9Ii4xMzQ1cHgiIGxldHRlci1zcGFjaW5nPSIwcHgiIHN0cm9rZS13aWR0aD0iLjAxNjgxIiB0ZXh0LWFsaWduPSJjZW50ZXIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHdvcmQtc3BhY2luZz0iMHB4IiBzdHlsZT0iZm9udC1mZWF0dXJlLXNldHRpbmdzOm5vcm1hbDtmb250LXZhcmlhbnQtY2Fwczpub3JtYWw7Zm9udC12YXJpYW50LWxpZ2F0dXJlczpub3JtYWw7Zm9udC12YXJpYW50LW51bWVyaWM6bm9ybWFsO2xpbmUtaGVpZ2h0OjEuMjUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSItNi42Mjk1NjYyIiB5PSIxMjUuMTUzNSIgc3Ryb2tlLXdpZHRoPSIuMDE2ODEiPmdpdG9oYXNoaTwvdHNwYW4+PC90ZXh0Pjx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKC03LjA5NykiIHg9Ii02LjYzMzMyNTYiIHk9IjEyNS4xNDgwNiIgZmlsbD0iIzk5OTk5OSIgZm9udC1mYW1pbHk9IidPcGVuIFNhbnMnIiBmb250LXNpemU9Ii4xMzQ1cHgiIGxldHRlci1zcGFjaW5nPSIwcHgiIHN0cm9rZS13aWR0aD0iLjAxNjgxIiB0ZXh0LWFsaWduPSJjZW50ZXIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHdvcmQtc3BhY2luZz0iMHB4IiBzdHlsZT0iZm9udC1mZWF0dXJlLXNldHRpbmdzOm5vcm1hbDtmb250LXZhcmlhbnQtY2Fwczpub3JtYWw7Zm9udC12YXJpYW50LWxpZ2F0dXJlczpub3JtYWw7Zm9udC12YXJpYW50LW51bWVyaWM6bm9ybWFsO2xpbmUtaGVpZ2h0OjEuMjUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSItNi42MzMzMjU2IiB5PSIxMjUuMTQ4MDYiIGZpbGw9IiM5OTk5OTkiIHN0cm9rZS13aWR0aD0iLjAxNjgxIj5naXRvaGFzaGk8L3RzcGFuPjwvdGV4dD48L2c+PGcgdHJhbnNmb3JtPSJtYXRyaXgoNC40NTYgLS41MTUgLjUxNSA0LjQ1NiAzNy4yNSAtMzIxLjkpIj48ZyB0cmFuc2Zvcm09Im1hdHJpeCgxLjkyIC43NTg5IC0uNzU4OSAxLjkyIC0yMzQuNyAtODg3LjUpIj48ZyB0cmFuc2Zvcm09InJvdGF0ZSgtOS4xOTYgMjkwLjUgNDEyLjIpIiBmaWxsPSIjNGQ0ZDRkIj48cGF0aCBkPSJtMjg5LjYgNDEyLjYgNC4xOS0xLjQ0IDAuMDIwOS02ZS0zczAuMDEyNi03LjhlLTQgMC4wMTgzIDguNmUtNGMwLjAyMjMgOWUtMyAwLjAxODUgMC4wMjA4IDAuMDE4MiAwLjAyNzItNGUtMyAwLjAxODEtMC4wMTQzIDAuMDIxNy0wLjAxNDMgMC4wMjE3bC00LjE2OSAxLjU5MS0wLjAxNDYtMC4wNzItMC4wMTQzLTAuMDQzMnoiLz48cGF0aCBkPSJtMjg5LjYgNDEyLjZjLTAuMDUyNCAwLjAyMjYtMC4wODYxIDAuMDg5OS0wLjA2MjcgMC4xNDM3IDAuMDMwNyAwLjA0NjkgMC4wNzg4IDAuMDYxNCAwLjEyNTQgMC4wNTExLTJlLTMgLTAuMDU2Ni0wLjAxMzYtMC4wOTQ5LTAuMDM1My0wLjE1MjItNWUtMyAtMC4wMS0wLjAyMzktMC4wMzQyLTAuMDI5NS0wLjA0MzZ6Ii8+PC9nPjxlbGxpcHNlIGN4PSIyODkuNyIgY3k9IjQxMi44IiByeD0iLjA2MzA3IiByeT0iLjA1OTQzIiBmaWxsPSJ1cmwoI2EpIi8+PC9nPjxwYXRoIGQ9Im04LjE0NCAxMjQuOSAwLjA3Ni0wLjA1ODUgMC4wNzAxNi0wLjAzNTEgOS4xMjEtMS4wNTIgMC4wNDY3NyAwLjAxMTctOC45ODYgMS4xNjMtMC4wNDA5My0wLjA0NjgtMC4wOTkzOS0wLjA0NjgtMC4wOTM1NCAwLjAxMTd6IiBmaWxsPSJ1cmwoI2IpIi8+PHBhdGggZD0ibTguMjM4IDEyNS4yIDAuMTQ2Mi0wLjA2NDMgMC4wNDY3Ny0wLjA0NjggOS4wMzktMS4yNjMtMC4wMzUwOCAwLjAzNTEtOS4wMDkgMS4zNDUtMC4xMjI4IDAuMDExN3oiIGZpbGw9IiM0MjQ4NGMiIGZpbGwtb3BhY2l0eT0iLjcyMTYiLz48dGV4dCB0cmFuc2Zvcm09InJvdGF0ZSgtNy4wOTcpIiB4PSItNi42Mjk1NjYyIiB5PSIxMjUuMTUzNSIgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9IidPcGVuIFNhbnMnIiBmb250LXNpemU9Ii4xMzQ1cHgiIGxldHRlci1zcGFjaW5nPSIwcHgiIHN0cm9rZS13aWR0aD0iLjAxNjgxIiB0ZXh0LWFsaWduPSJjZW50ZXIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHdvcmQtc3BhY2luZz0iMHB4IiBzdHlsZT0iZm9udC1mZWF0dXJlLXNldHRpbmdzOm5vcm1hbDtmb250LXZhcmlhbnQtY2Fwczpub3JtYWw7Zm9udC12YXJpYW50LWxpZ2F0dXJlczpub3JtYWw7Zm9udC12YXJpYW50LW51bWVyaWM6bm9ybWFsO2xpbmUtaGVpZ2h0OjEuMjUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSItNi42Mjk1NjYyIiB5PSIxMjUuMTUzNSIgc3Ryb2tlLXdpZHRoPSIuMDE2ODEiPmdpdG9oYXNoaTwvdHNwYW4+PC90ZXh0Pjx0ZXh0IHRyYW5zZm9ybT0icm90YXRlKC03LjA5NykiIHg9Ii02LjYzMzMyNTYiIHk9IjEyNS4xNDgwNiIgZmlsbD0iIzk5OTk5OSIgZm9udC1mYW1pbHk9IidPcGVuIFNhbnMnIiBmb250LXNpemU9Ii4xMzQ1cHgiIGxldHRlci1zcGFjaW5nPSIwcHgiIHN0cm9rZS13aWR0aD0iLjAxNjgxIiB0ZXh0LWFsaWduPSJjZW50ZXIiIHRleHQtYW5jaG9yPSJtaWRkbGUiIHdvcmQtc3BhY2luZz0iMHB4IiBzdHlsZT0iZm9udC1mZWF0dXJlLXNldHRpbmdzOm5vcm1hbDtmb250LXZhcmlhbnQtY2Fwczpub3JtYWw7Zm9udC12YXJpYW50LWxpZ2F0dXJlczpub3JtYWw7Zm9udC12YXJpYW50LW51bWVyaWM6bm9ybWFsO2xpbmUtaGVpZ2h0OjEuMjUiIHhtbDpzcGFjZT0icHJlc2VydmUiPjx0c3BhbiB4PSItNi42MzMzMjU2IiB5PSIxMjUuMTQ4MDYiIGZpbGw9IiM5OTk5OTkiIHN0cm9rZS13aWR0aD0iLjAxNjgxIj5naXRvaGFzaGk8L3RzcGFuPjwvdGV4dD48L2c+PC9nPjwvc3ZnPg=="); 5 | width:0px; 6 | height:0px; 7 | padding:1.7em 2.8em; 8 | vertical-align:middle; 9 | margin-right: 0.3em; 10 | margin-bottom: 8px; 11 | background-repeat: no-repeat; 12 | color: rgba(0, 0, 0, 0); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /cmake/config.h.in: -------------------------------------------------------------------------------- 1 | #cmakedefine JG2_HAVE_ARCHIVE_H 2 | #cmakedefine JG2_HAVE_BLAME_MAILMAP 3 | #cmakedefine JG2_HAS_PTHREAD_SETNAME_NP -------------------------------------------------------------------------------- /doc/README-JSON.md: -------------------------------------------------------------------------------- 1 | ## JSON output 2 | 3 | The library produces well-formed JSON ready for `JSON.parse()` that 4 | always has an outer container like this 5 | 6 | ``` 7 | { 8 | "schema":"jg2-1", 9 | ... dynamic entries ... 10 | "items": [ 11 | 12 | ... one or more "job" JSON `{ structures }`, may be from cache ... 13 | 14 | ], 15 | ... dynamic stats entries ... 16 | } 17 | ``` 18 | 19 | ### Dynamic JSON header entries 20 | 21 | JSON name|Meaning 22 | ---|--- 23 | schema|always "jg2-1" currently 24 | vpath|The virtual URL path the server is at, eg, "/git/". URL paths back to the server must start with this 25 | avatar|URL path to link to the avatar cache, eg, "/avatar/" 26 | alang|Whatever the browser gave to us for `ACCEPT_LANGUAGE` 27 | f|server flags... b0 = 1: server can handle blame, b1 = 1 = server can do snapshot archives, b2 = 1 = blog mode 28 | gen_ut|Unix time this page was created 29 | reponame|Name of the repository this refers to 30 | desc|Description of the project in the repository from gitweb 31 | owner|`{identity}` structure (see later) using data from gitweb 32 | url|git clone url from gitweb 33 | 34 | Note on `"f"`... libjsongit2 builds adaptively to the featureset in the version 35 | of libgit2 that it's linked to and whether libarchive was present. b0 and b1 36 | let us know if the server can handle blame (libgit > 0.21) and creating 37 | snapshot archives (libarchive required), so the UI can adapt accordingly. 38 | 39 | b2, "blog mode" parses markdown in the git repo as a semi-static blog; if set 40 | it implies a simplified UI should be shown, eg, without the mode tabs or tree 41 | directory view. 42 | 43 | ### Dynamic JSON trailer entries 44 | 45 | JSON name|Meaning 46 | ---|--- 47 | g|us (microseconds) taken to produce the overall JSON 48 | chitpc|Percentage of JSON cache hits for this vhost 49 | ehitpc|Percentage of ETAG cache hits for this vhost 50 | 51 | ### Identity structure 52 | 53 | JSON name|Meaning 54 | ---|--- 55 | name|Name like "Fred Bloggs" 56 | email|Email like fred@bloggs.com 57 | md5|md5sum of the email (used with gravatar) 58 | 59 | ### OID structure 60 | 61 | JSON name|Meaning 62 | ---|--- 63 | oid|OID the information applies to 64 | alias|List of refs that share this OID 65 | 66 | ### git_time structure 67 | 68 | JSON name|Meaning 69 | ---|--- 70 | time|Unix time 71 | offset|Timezone offset from unix time in minutes 72 | 73 | ### Signature structure 74 | 75 | JSON name|Meaning 76 | ---|--- 77 | git_time|`{git_time structure}` 78 | name|Name 79 | email|email 80 | md5|md5sum of the email 81 | 82 | ### Job JSON structure 83 | 84 | A single URLpath "connection context" may need to spawn multiple "jobs" in a 85 | single response. For example, if there is a README.md in the directory, the 86 | `"items": []` array will contain two JSON items, one from a "/tree/" job to show 87 | the directory contents, and another showing the contents of the README.md blob 88 | for the revision being shown. 89 | 90 | The contents of the Job JSON depends on the job type... and the kind of jobs 91 | that can appear are set by the URLpath the JSON is returned for. Eg the JSON 92 | for a tree directory listing is (for a `/tree/` URL) is like this: 93 | 94 | ``` 95 | { "schema":"libjg2-1", 96 | "oid":{ "oid": "79397b21a38d263263a65abb69b8e1fffd326796", 97 | "alias": [ "refs/heads/master"]},"tree": [ 98 | { "name": "READMEs","mode": "16384", "size":0}, 99 | { "name": "cmake","mode": "16384", "size":0}, 100 | { "name": "contrib","mode": "16384", "size":0}, 101 | ... 102 | { "name": "component.mk","mode": "33188", "size":1659}, 103 | { "name": "libwebsockets.dox","mode": "33188", "size":11618}], 104 | "s":{"c":1534293661,"u":2442}} 105 | ``` 106 | 107 | ### Job JSON header 108 | 109 | Ever job JSON starts with the same header, which also goes into the cached 110 | version. 111 | 112 | JSON name|Meaning 113 | ---|--- 114 | schema|"libjg2-1" 115 | cid|If cache enabled, and relevant to the job, the cache hash of this JSON 116 | oid|`{OID structure}` described above; present if content is related to an oid 117 | 118 | ### Job JSON trailer 119 | 120 | Every "job" JSON has this information appended and the information is also 121 | part of the cached copy. 122 | 123 | JSON name|Meaning 124 | ---|--- 125 | c|Creation Unix Time 126 | u|us (microseconds) taken to create originally 127 | 128 | ## Job-specific JSON reference 129 | 130 | ### tree directory 131 | 132 | Outer JSON name: **tree** 133 | 134 | Comprises an array of structures of the form 135 | 136 | JSON name|Meaning 137 | ---|--- 138 | name|The file name in the directory 139 | mode|low 9 bits are xrw bits for owner, group, other, b14 means directory 140 | size|size of the blob in bytes 141 | 142 | ### tree file 143 | 144 | JSON name|Meaning 145 | ---|--- 146 | blobname|The file name of the blob 147 | blob|the JSON-escaped content of the blob 148 | 149 | ### repo list 150 | 151 | Outer JSON name: **repolist** 152 | 153 | Comprises an array of structures of the form 154 | 155 | JSON name|Meaning 156 | ---|--- 157 | reponame|The name of the repo (xyz.git/ would have the reponame "xyz") 158 | desc|The gitweb description 159 | name|Gitweb owner name 160 | email|Gitweb owner email 161 | md5|md5 of the gitweb owner email 162 | url|clone URL 163 | 164 | ### reflist 165 | 166 | A list of refs from the repo 167 | 168 | Outer JSON name: **reflist** 169 | 170 | Comprises an array of structures of the form 171 | 172 | JSON name|Meaning 173 | ---|--- 174 | name|The name of the ref, eg "refs/heads/master" 175 | summary|A `{summary}` JSON struct, see below 176 | 177 | The `{summary}` struct for a branch looks like 178 | 179 | JSON name|Meaning 180 | ---|--- 181 | type|"commit" 182 | time|The unix time of the commit 183 | time_ofs|The timezone offset in minutes 184 | oid_tree|The `{OID structure}` for the tree at this rev 185 | oid|The `{OID structure}` for the commit at this rev 186 | msg|The short commit log message 187 | sig_commit|The `{signature structure}` for the committer 188 | sig_author|The `{signature structure}` for the author 189 | 190 | The `{summary}` struct for a tag looks like 191 | 192 | JSON name|Meaning 193 | ---|--- 194 | type|"tag" 195 | oid_tag|The `{OID structure}` for the tagged commit 196 | type_tag|"commit" 197 | msg_tag|The short commit log message 198 | sig_tagger|The `{signature structure}` for the tagger 199 | 200 | ### log 201 | 202 | This is a series of commits 203 | 204 | Outer JSON name: **log** 205 | 206 | Comprises an array of structures of the form 207 | 208 | JSON name|Meaning 209 | ---|--- 210 | name|The `{OID structure}` for the commit at this rev 211 | summary|A `{summary}` JSON struct, see below 212 | 213 | The `{summary}` struct contains 214 | 215 | JSON name|Meaning 216 | ---|--- 217 | type|"commit" 218 | time|The unix time of the commit 219 | time_ofs|The timezone offset in minutes 220 | oid_tree|The `{OID structure}` for the tree at this rev 221 | oid|The `{OID structure}` for the commit at this rev 222 | msg|The short commit log message 223 | sig_commit|The `{signature structure}` for the committer 224 | sig_author|The `{signature structure}` for the author 225 | 226 | ### blame 227 | 228 | This information is provided after a "job" delivering the unannotated blob 229 | for the file being blamed. 230 | 231 | Outer JSON name: **blame** and **contrib** 232 | 233 | The first `"blame": []` section comprises an array of structures of the form 234 | 235 | JSON name|Meaning 236 | ---|--- 237 | ordinal|sort-order ordinal 238 | orig_oid|The `{OID structure}` for the pre-patched content 239 | final_oid|The `{OID structure}` for the post-patched content 240 | sig_orig|The `{signature structure}` for the original content patch 241 | sig_final|The `{signature structure}` for the final content patch 242 | log_final|The short commit log message for the final content patch 243 | op|Original patch filepath for the content (in the case of file rename or move) 244 | ranges|An array of `{blame_range}` structures (see below) 245 | 246 | The `{blame_range}` struct consists of 247 | 248 | JSON name|Meaning 249 | ---|--- 250 | l|the number of lines 251 | o|The start line number from the original file / revision 252 | f|The start line number in the revision of the file being blamed 253 | 254 | after the `"blame": []` array there is a second array `"contrib": []`, which 255 | comprises an array of structures of the form: 256 | 257 | JSON name|Meaning 258 | ---|--- 259 | l|The number of lines still remaining in the file from this contributor 260 | o|The ordinal of the `blame: []` section entry whose `sig_final` member was by this contributor 261 | 262 | NOTES: The first section is pre-sorted into order of first appearence in the 263 | file being blamed. It represents a patch which can have multiple discontiguous 264 | line ranges in the version of the file currently being blamed, described in 265 | its `"ranges": []` member. 266 | 267 | After that information, the `"contrib": []` array is a list of all individual 268 | contributors to the current file state, sorted by the number of lines of text 269 | they have contributed. If your libgit2 is recent enough (master or 0.28+) then 270 | this list takes into account any .mailmap in the top level git in HEAD. 271 | -------------------------------------------------------------------------------- /doc/README-build.md: -------------------------------------------------------------------------------- 1 | # Building gitohashi 2 | 3 | ## Dependency packages 4 | 5 | ![gitohashi build deps](./doc-assets/deps-goh.svg) 6 | 7 | - libgit2 8 | 9 | https://libgit2.org/ 10 | 11 | - libwebsockets (already in distros but requires master or v3.1 +) 12 | 13 | https://libwebsockets.org/git/libwebsockets 14 | 15 | These are both easy-to-build cmake projects like gitohashi. 16 | 17 | ## Build 18 | 19 | ### Step 1: install build packages 20 | 21 | Distro|Dependency Package name 22 | ---|--- 23 | Fedora | cmake, libgit2, libgit2-devel, libarchive, libarchive-devel 24 | Ububtu 14.04 | cmake, libgit2-0, libgit2-dev, libarchive13, libarchive-dev 25 | Ubuntu 16.04 | cmake, libgit2-24, libgit2-dev, libarchive13, libarchive-dev 26 | 27 | #### Note on libgit2 versions 28 | 29 | libjsongit2 support libgit2 going back to v0.19 found in Ubuntu 14.04 and up 30 | to current master. 31 | 32 | Blame support requires libgit2 version >=0.21, but libjsongit2 adapts to 33 | versions older than that by gracefully disabling blame. 34 | 35 | 0.28+ (and master libgit2) support `.mailmap` integration with blame, again if 36 | it's not available libjsongit2 blame still works without it. 37 | 38 | If you want to build a later, local libgit2 to get these features, it is also a 39 | cmake project that's easy to build the same way as libjsongit2 itself. 40 | 41 | You can direct libjsongit2 to build using your local libgit2 instead of the 42 | packaged version like this: 43 | 44 | ``` 45 | $ cmake .. -DJG2_GIT2_INC_PATH=/usr/local/include \ 46 | -DJG2_GIT2_LIB_PATH=/usr/local/lib/libgit2.so 47 | ``` 48 | 49 | ### Step 2: clone, build, install 50 | 51 | One dependent project and gitohashi need to be built, but both of them can be 52 | built will cmake using default options simply. Libwebsockets is available 53 | packaged in most distros, but we need the latest version to support gitohashi. 54 | 55 | Order|Project|Clone command 56 | ---|---|--- 57 | 1|libwebsockets| `git clone https://libwebsockets.org/repo/libwebsockets` 58 | 2|gitohashi| `git clone https://warmcat.com/repo/gitohashi` 59 | 60 | enter the cloned dir for each in turn and build like this: 61 | 62 | ``` 63 | $ mkdir build 64 | $ cd build 65 | $ cmake .. (for libwebsockets, `cmake .. -DLWS_FOR_GITOHASHI=1`) 66 | $ make && sudo make install 67 | ``` 68 | 69 | NOTE1: You can configure the daemon or other project to be built with symbols using 70 | ``` 71 | $ cmake .. -DCMAKE_BUILD_TYPE=DEBUG 72 | ``` 73 | 74 | NOTE2: If you are directly serving, HTTP/2 is advantageous performance-wise on 75 | pages where there are a lot of avatars or other fetches going on from the same 76 | server... you can enable this on libwebsockets simply with 77 | `cmake .. -DLWS_WITH_HTTP2=1` instead of the `cmake ..` step. 78 | 79 | NOTE3: On BSD / OSX the OpenSSL is in a strange place... you need to inform 80 | the gitohashi build. You can just do 81 | 82 | ``` 83 | $ export CFLAGS="-I/usr/local/opt/openssl/include" 84 | ``` 85 | 86 | before the build. 87 | -------------------------------------------------------------------------------- /doc/README-cache.md: -------------------------------------------------------------------------------- 1 | ## Transparent JSON Cache 2 | 3 | If the vhost config member `json_cache_base` is non-NULL, then it is used as 4 | the base path for a transparent JSON cache. When a job starts to generate some 5 | content, a hash is generated using the following keys 6 | 7 | 1. An epoch number set in libjsongit2 that allows whole caches to be 8 | invalidated when a mandatory format change occurs on a later version 9 | 10 | 2. The job type (generate a commit view, or a snapshot, etc) 11 | 12 | 3. The count (some jobs generate a requested amount of output only) 13 | 14 | Then, if the urlpath implies a specific repo 15 | 16 | 4. The repo refs hash, a hash generated from all ref oids and names in the 17 | repo 18 | 19 | 5. repo dir (the filesystem dir of the repo, eg, /srv/repositories/myrepo.git) 20 | 21 | 6. the urlpath mode (blame, tree, etc) 22 | 23 | 7. urlpath filepath (the file inside the repo we are looking at) 24 | 25 | If the urlpath did not imply a specific repo, then 26 | 27 | 8. The HEAD oid of gitolite-admin, if any 28 | 29 | 9. Every repo name accessible using the current vhost and ctx acl user. 30 | 31 | The generated hash becomes the filename in the cache dir for the content. 32 | 33 | Later, requests for jobs also have their hash computed the same way, only if 34 | everything is the same (including the refs state of the repo...) will the 35 | cached hash be arrived at the same. 36 | 37 | Cache files are created with an appended pid and pointer to the ctx requesting 38 | the job, since it's possible multiple contexts or other instances could have 39 | also been asked to create the context before a cache entry exists. When the 40 | cache content generation is complete, the temp filename is renamed to the 41 | final cacahe filename... if this fails because another instance got there first, 42 | the temp cache file is simply deleted. 43 | 44 | ### Scope of cache 45 | 46 | The cache operates on "content generated by a libjsongit2 job", usually JSON, 47 | but it also operates on /plain/ (directly serve files from a repo with an 48 | appropriate mimetype) and /snapshot/ (generate tarballs and zipfiles for a repo 49 | ref). 50 | 51 | It deliberately does not cover the vhost HTML template, the css, js etc loaded 52 | by that nor the outer JSON braces and content there generated by libjsongit2. 53 | 54 | This allows old cached content, still reflecting the current repo state, to be 55 | served using the newest HTML, CSS, JS etc the same as freshly-generated content. 56 | libjsongit2 also adds up-to-date dynamic information about page generation time 57 | and cache statistics in the outer JSON whether the page contains some or 58 | otherwise all cached JSON content; this is shown in the footer in the example 59 | HTML template. 60 | 61 | ### Cache hashes and ETAG 62 | 63 | libjsongit2 can compute the cache hash for URL + repo state output without 64 | actually generating the contents. This allows the cache hash to also be used 65 | with [HTTP's RFC7232 ETAG](https://tools.ietf.org/html/rfc7232) scheme, where the browser is given the 66 | cache hash along with the contents, and if it asks for it again where no 67 | prerequisite has changed, can simply be told in the HTTP response code that 68 | the version the browser has in its local cache is still current. 69 | 70 | This allows requests for even dynamically-generated content like /plain/ and 71 | /snapshot/ URLs to be completed with just the cache hash generation and 72 | comparison, without doing any work or sending anything except the HTTP 304 73 | notification, if the browser already has the same version we would have 74 | generated and sent. This is particularly effective when, eg, the README.md 75 | in the root tree view has many pictures served from the versioned repo itself 76 | and the user passes through it multiple times using the tree part. 77 | 78 | ### Cache maintenance 79 | 80 | The amount of storage the cache is allowed to use can be limited using the 81 | `.cache_size_limit` of `struct jg2_vhost_config` at vhost creation time. 82 | 83 | A thread lazily scans the cache subdirs and sleeps for a second after each one. 84 | Since there are 16 x 16 cache subdirs, it completes a scan every 256s (around 85 | 4 minutes). As the cache is managed by LRU, the thread takes the approach 86 | to only collect the names the 128 oldest files, regardless of how many files 87 | there actually are. So the cache management action is cheap, done lazily and 88 | only uses a maximum of around 12KiB heap. 89 | 90 | If at the end of the scan the thread sees the total storage exceeds the maximum, 91 | it unlinks files on its list, starting with the oldest first until it's back 92 | under the limit. If 128 files weren't enough to get it under the limit, it will 93 | start the next scan immediately and run it doing 8 cache subdirectories per 94 | second (30s) until the excess is cleared. 95 | 96 | If the cache is not near the maximum, scans are delayed according to an estimate 97 | of how long it would take to reach the maximum, for no longer than an hour. 98 | 99 | ### Keeping bot spidering out of the JSON cache 100 | 101 | If the libjsongit2 context is created with the flag bit JG2_CTX_FLAG_BOT set, 102 | then although the access can use already-cached items, it is disabled from 103 | creating new things in the cache. This is because aggressive random spidering 104 | will generate continuous "noise" cache content where the access has no 105 | implication that another user may consider the content interesting in the 106 | future, flushing out user-generated content that does imply it may be accessed 107 | again. -------------------------------------------------------------------------------- /doc/README-gitolite.md: -------------------------------------------------------------------------------- 1 | ### Notes on gitolite vs gitolite3 2 | 3 | Gitohashi requires gitolite v3+. 4 | 5 | The "gitolite" package found in Fedora and older Ubuntu is actually gitolite 6 | v2, from many years ago. Fedora since F16 and Ubuntu since Xenial provide a 7 | "gitolite3" package which is current gitolite. 8 | 9 | If you're still on v2, you'll need to go through a [one-way upgrade process](http://gitolite.com/gitolite/migr/index.html#migrating-from-gitolite-v2). 10 | 11 | v2 and v3 broadly allows mostly the same config syntax, although there are 12 | some removed v2 features in the .rc file and new v3 features, see [this](http://gitolite.com/gitolite/migr/index.html#step-4-identify-any-remaining-changes-needed) 13 | 14 | Additionally: 15 | 16 | - anglebrackets and parenthesis are disallowed in config strings in v3. 17 | This disallows the common email format "Name "... gitohashi 18 | understands "Name name@email.com" as well in order to get around this. 19 | 20 | ### Selecting which repos to show on a vhost 21 | 22 | By default, no repo is allowed to be shown, even when the vhost has supplied the 23 | `repo_base_dir` config. 24 | 25 | You can override this simply to get started by providing the vhost `.acl_user` 26 | name of "@all" as the example code does. This will allow libjsongit2 to show 27 | the contents of any repo in the `repo_base_dir`. 28 | 29 | For a real server, libjsongit2 uses gitolite ACLs to control which vhosts 30 | can see what. It's not uncommon for a server to have several HTTP vhosts, but 31 | only a single system like gitolite handling repos in a single directory. 32 | 33 | So it becomes important to restrict the view of the different vhosts to the 34 | repos that are relevant to them. Rather than reinvent the wheel, libjsongit2 35 | lets you configure what it will show using gitolite config. 36 | 37 | ![Using gitolite for ACL](./doc-assets/libjsongit2-acl.svg) 38 | 39 | If the gitolite well-known repo `gitolite-admin` is present in the repo 40 | directory, it is parsed automatically using the vhost's `acl_user` to discover 41 | the ACLs possible for this vhost. 42 | 43 | Additionally, when creating the context for a specific transaction, you can 44 | pass in an authorized username for the transaction using the `authorized` 45 | parameter in the context creation api. If non-NULL, this adds to the repos 46 | already available from selecting using the vhost `acl_user` name. 47 | 48 | #### Allow gitolite to set repo gitweb config 49 | 50 | Gitohashi acquires gitweb repo information from the bare repo config file, at 51 | `myrepo.git/config`. You can set these by hand using git config syntax if you 52 | want, by adding a section like this... 53 | 54 | ``` 55 | [gitweb] 56 | description = libwebsockets lightweight C networking library 57 | url = https://libwebsockets.org/repo/libwebsockets 58 | owner = Andy Green 59 | ``` 60 | 61 | ...but if you are already using gitolite, rather than have to ssh in and edit 62 | the bare repo config each time, it's much more convenient to define and manage 63 | these in the same gitolite config file used for repo creation and ACLs already. 64 | 65 | Gitolite supports it but to allow the gitolite config file to alter the bare 66 | repo config, you must one-time whitelist the gitweb-related keys we want to 67 | control from there in `.gitolite.rc` in your gitolite user home dir. 68 | 69 | gitolite v2.x 70 | ``` 71 | $GL_GITCONFIG_KEYS = "gitweb.description gitweb.owner gitweb.url"; 72 | ``` 73 | 74 | gitolite v3.x 75 | ``` 76 | $GIT_CONFIG_KEYS = "gitweb.description gitweb.owner gitweb.url"; 77 | ``` 78 | 79 | Then, in your usual gitolite config you can do this kind of thing 80 | 81 | ``` 82 | repo libwebsockets 83 | RW+ = @ag 84 | R = v-lws 85 | config gitweb.description = "libwebsockets lightweight C networking library" 86 | config gitweb.owner = "Andy Green andy@warmcat.com" 87 | config gitweb.url = "https://libwebsockets.org/repo/libwebsockets" 88 | ``` 89 | 90 | When you update the gitolite-admin remote repo, it will apply the defined 91 | config into the remote repos' config immediately. In pages rendered by 92 | gitohashi, the updated gitweb information will be reflected immediately. 93 | 94 | #### Set up per-vhost virtual users 95 | 96 | libjsongit2 can understand gitolite ACLs for the repos. You can associate a 97 | libjsongit2 vhost with a user name in your gitolite config, and that vhost will 98 | show on the web any repo that the user name has access to. 99 | 100 | The user name doesn't have to be a real user with ssh keys. 101 | 102 | Eg, here we give the "user" v-lws read access to `libwebsockets`... 103 | 104 | ``` 105 | repo libwebsockets 106 | RW+ = @ag 107 | R = v-lws 108 | ``` 109 | 110 | if we tell the libjsongit2 vhost its acl-user is also `v-lws`, it will 111 | understand it should serve the `libwebsockets` repo and any other repos `v-lws` 112 | has read access to. 113 | 114 | No other repo will be opened by gitohashi for serving. 115 | 116 | -------------------------------------------------------------------------------- /doc/README-libjsongit2.md: -------------------------------------------------------------------------------- 1 | libjsongit2 2 | ----------- 3 | 4 | Gitohashi contains a lightweight C library using libgit2 that converts 5 | urlpaths into JSON representing the "business logic" of a sophisticated gitweb 6 | type interface. Gitohashi uses its library and provides assets to present its 7 | JSON representation in HTML, but libjsongit2's JSON representation could also 8 | be represented completely different by different applications using it. 9 | 10 | ![libjsongit2-overview](./doc/doc-assets/libjsongit2-overview.svg) 11 | 12 | Try it out via gitohashi at https://libwebsockets.org/git and 13 | https://warmcat.com/git 14 | 15 | ## Features 16 | 17 | - library implements a "gitweb" type interface to bare git repos, taking a 18 | cgit-compatible "urlpath" and returning JSON, an HTML template plus JSON, 19 | or a direct file from inside a repo revision with its own mimetype, 20 | with the presentation entirely separated and done clientside. 21 | 22 | - Fast, small, modern, stateful, multivhost, threadsafe, opaque C api with 23 | just 5 functions, no libgit2 types. 24 | 25 | - Works with libgit2 v0.19+ ie, Ubuntu 14.04+; blame support requires 26 | libgit2 0.21+ 27 | 28 | - Per-connection stateful context-based api bound to a vhost allows unlimited 29 | number of concurrent ongoing requests on same or different repos 30 | 31 | - Output generated JIT to fill user buffer, state maintain to generate more 32 | only when the user code wants to fill another buffer. 33 | 34 | - Transparent caching at JSON block level, keyed using global repository ref 35 | state... cache invalidated when any ref updated. No time-based caching. 36 | 37 | - Top level vhost context allows small number of settings like vurl, and 38 | caching things per-vhost. If using HTML sandwiching, the vhost loads the 39 | html template into memory one time. 40 | 41 | - Uses existing repo configuration where possible: 42 | 43 | - gitweb config options in the repo itself 44 | 45 | - gitolite ACL config parsed if found in the vhost's repository directory 46 | selects which repos are visible based on vhost's "user" and per-context 47 | authenticated user 48 | 49 | - Very modest on memory, Valgrind-clean, Coverity-clean 50 | 51 | - Highly optimized Gravatar / md5 support 52 | 53 | - Supports serving http assets like pictures directly from the repo, so 54 | they are versioned along with the ref being viewed 55 | 56 | - Dynamic, stateful snapshot archive generation for tar.gz, tar.bz2, tar.xz 57 | and zip 58 | 59 | - Enables Internationalization, passes the browser `ACCEPT_LANGUAGE` header 60 | data back in the JSON. But since internationalization is about presentation, 61 | it is actually performed clientside. 62 | 63 | - various alignments to github style, such as: 64 | 65 | - inline README.md display in tree view 66 | 67 | - markdown can fetch from the repo with URLs starting ./ for the 68 | current directory the markdown is at inside the repo 69 | 70 | - line range highlighting 71 | 72 | - sorted contributor list in blame view 73 | 74 | - CMake crossplatform, simple, adaptive build system 75 | 76 | ## Getting Started 77 | 78 | libjsongit2 is a cmake project that's easy to build. It requires libgit2 and 79 | optionally libarchive, both of which are available in all popular distros. 80 | 81 | Full build details: [README-build.md](./doc/README-build.md) 82 | 83 | ## Overview 84 | 85 | This C library gives you a way to get Read-Only access to selected repos 86 | according to your gitolite ACLs, in HTML + JSON and also to directly access repo 87 | files for a particular rev in their native mimetype. From a URL path, it 88 | returns serveable HTTP reflecting various ways of looking at the repo and its 89 | contents as needed by a gitweb type application. 90 | 91 | ![naked-and-sandwich](./doc/doc-assets/naked-and-sandwich.svg) 92 | 93 | After initializing at least vhost with this library, you create per-connection 94 | "contexts" which are associated with a repo in the filesystem and a "urlpath" 95 | describing the information you want in JSON. 96 | 97 | Based on the urlpath, the library emits one or more "jobs", either HTML + JSON 98 | or raw files served from a repo rev. Each time there's no more space left in 99 | the user output buffer for another entry, the library returns and waits to be 100 | called again to fill another buffer. So the library does not create content 101 | until it's directly possible to send it on, and the caller is explicitly in 102 | control of how much data is produced and when. Many different connection 103 | contexts may be ongoing simultaneously. 104 | 105 | The library uses the urlpath to provide all the kinds of information needed 106 | for gitweb style rendering at the client. 107 | 108 | Jobs can be chained together so different kinds of information can coexist in a 109 | single JSON blob. 110 | 111 | The context holds all the necessary state, the user code chooses when to ask a 112 | context to continue to generate another buffer of JSON. So it's able to only do 113 | work (and use memory) to generate data that can be sent onwards immediately, 114 | eliminating stream buffering at the server side. 115 | 116 | ### HTML template "sandwich" 117 | 118 | To simplify customization and use, libjsongit2 can produce JSON already 119 | embedded in a template HTML. An HTML comment marks where the JSON should be 120 | inserted. 121 | 122 | ![libjsongit2-sandwich](./doc/doc-assets/libjsongit2-sandwich.svg) 123 | 124 | The template HTML also makes it easy to directly configure css and related js 125 | needed both by the template and libjsongit2 parts in the template itself. 126 | 127 | The HTML template is cached in memory by the libjsongit2 vhost, but it checks 128 | each time it's used if it has been longer than 5s since the last check, that 129 | it's up to date with the original file in the filesystem, and auto-reloads if 130 | not. 131 | 132 | ### JSON output 133 | 134 | The library produces well-formed JSON ready for `JSON.parse()` that 135 | always has an outer container like this 136 | 137 | ``` 138 | { 139 | "schema":"jg2-1", 140 | ... dynamic entries ... 141 | "items": [ 142 | 143 | ... one or more "job" JSON { structures }, may be from cache ... 144 | 145 | ], 146 | ... dynamic stats entries ... 147 | } 148 | ``` 149 | 150 | cacheable "items" that are relevant to the requested urlpath are provided. 151 | 152 | Full details and documentation on the JSON format: [README-JSON.md](./doc/README-JSON.md) 153 | 154 | The jg2-example application built along with libjsongit2 allows you to see the 155 | JSON produced for a given URLpath. 156 | 157 | ### URL structure 158 | 159 | The generated JSON embeds links and interprets url paths (the part after 160 | `http[s]://xxx.com`) using the following rules, broadly compatible with cgit: 161 | 162 | **/vpath**/reponame/**mode**/repopath\[**?h=branch**\] 163 | 164 | - vpath: the server can set `.virtual_base_urlpath` when creating the 165 | "vhost" using the `struct jg2_vhost_config *` passed as 166 | an argument to `jg2_vhost_create()` 167 | 168 | - reponame: which repo 169 | 170 | - mode: one of: 171 | - (the url ends before this): see summary 172 | - "refs": exhaustive list of refs in the repo 173 | - "log": history from a specific ref or commit 174 | - "tree": view of the file structure behind the commit chain 175 | - "commit": the actual diff view of a single commit 176 | - "plain": a blob with a guessed mimetype 177 | - "patch": plain text raw patch (text/plain mimetype) 178 | - "snapshot": various kinds of archive of a specific ref or commit 179 | - "blame": like tree but with extra provonance information 180 | - "branches": exhaustive list of branches 181 | - "tags": exhaustive list of tags 182 | - "summary": rundown of the top ten most-recently updated branches and tags 183 | 184 | - repopath: the path inside the repo 185 | 186 | - URLargs that restrict the context of the request include: 187 | - `?h=branch`: specifies a branch (default is "master") 188 | - `?id=` 189 | - `?ofs=` 190 | 191 | 192 | ### Gravatar support 193 | 194 | The library maintains a hashtable of most recently seen email md5s in the opaque 195 | "vhost" structure. By default, this is 16 hash bins each a max of 16 deep, or 196 | 256 md5s, or about 20KiB including space for 64-char emails. These numbers can 197 | be overridden at `jg2_vhost_create()` time. 198 | 199 | Because it's in the `jg2_vhost` struct, which contexts bind to at creation time, 200 | the email cache is shared between all contexts using the same vhost. 201 | 202 | The email cache algorithm moves email matches to the start of the bin's linked- 203 | list each time, and after the hash bin is full, it recycles the least-recently 204 | seen email at the end of the list. 205 | 206 | It's also possible to override the internal md5 code with an external function 207 | that may be faster, in the user config at the vhost init / vhost creation time. 208 | See `struct jg2_vhost_config` in `libjsongit2.h` 209 | 210 | [Gitohashi](https://warmcat.com/git/gitohashi) additionally provides an avatar proxy. 211 | 212 | ## API overview 213 | 214 | The user api is defined in `./include/libjsongit2.h`, it consists of just 5 215 | functions. 216 | 217 | To init: 218 | 219 | - `jg2_vhost_create()` once per vhost, but at least once (eg, on app init). 220 | An args struct is passed which allows configuring vhost options in an 221 | extensible way. If the app starts as root and drops privileges, the vhosts 222 | should be created while still root, allowing low tcp port binding and auto 223 | creation and persmissions setting on the JSON cache dir. 224 | 225 | - `jg2_ctx_create()` once per connection. An args struct is passed which 226 | allows configuring the connection options in an extensible way. When the 227 | context is created, the URL related to it is parsed to understand what kind 228 | of result is needed, along with returning the mimetype and size if possible 229 | to the caller. 230 | 231 | To emit the output (JSON, HTML + JSON, raw file contents etc): 232 | 233 | - `jg2_ctx_fill()` to write the next chunk of output into a provided buffer. 234 | As far as possible output is only generated a buffer at a time. 235 | 236 | To finish up: 237 | 238 | - `jg2_ctx_destroy()` for every created context (eg, on connection close) 239 | 240 | - `jg2_vhost_destroy()` for every vhost init (eg, on app close) 241 | 242 | ## Configuration 243 | 244 | libjsongit2 tries to eliminate as much configuration as possible. It does this 245 | by re-using existing git-related conventions like gitweb config and gitolite 246 | ACLs, instead of repeating separate lists of repos to be shown. 247 | 248 | ### Per-vhost configuration 249 | 250 | When creating the vhost object, there's a public `struct jg2_vhost_config` 251 | config struct that can be filled in, but only two items are mandatory. 252 | 253 | - `virtual_base_urlpath`: the virtual URL part, eg, /git 254 | 255 | - `repo_base_dir`: the base directories where the git repos live 256 | 257 | See https://warmcat.com/git/libjsongit2/tree/include/libjsongit2.h for the 258 | full set of vhost configuration arguments. 259 | 260 | Full-details of gitolite integration: [README-gitolite.md](./doc/README-gitolite.md) 261 | 262 | ### Example app 263 | 264 | A minimal example commandline app is built with the library, if you point 265 | it to a dir where bare repos live, and give it a "url path", it will dump 266 | JSON to stdout. 267 | 268 | See ./examples/minimal/jg2-example.c 269 | 270 | ## Transparent JSON Cache 271 | 272 | libjsongit2 can cache the results of individual JSON jobs and "naked" 273 | generated files like snapshots and content served direct from a repo ref. 274 | 275 | The user can set the cache location and size limit per repository base dir, 276 | and a thread scans the cache lazily deleting files on an LRU basis once the 277 | cache reaches its limit. The scan uses a few KiB of memory regardless of the 278 | size of the cache. 279 | 280 | The cached files use many keys to ensure they are relevant to the requested 281 | URL. Full details: [README-cache.md](./doc/README-cache.md) 282 | 283 | ## Contact 284 | 285 | Andy Green <andy@warmcat.com> 286 | 287 | -------------------------------------------------------------------------------- /doc/doc-assets/libjsongit2-acl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | image/svg+xmlgitolite-adminrepo1repo2repo3repo4repo5vhost"acl_user"v-myhostcontext"authorized"myuserrepo repo1 R = v-myvhostrepo repo5 R = v-myvhostrepo repo3 R = myusereffectiveACLGitolite ACL integration 4 | -------------------------------------------------------------------------------- /doc/doc-assets/naked-and-sandwich.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Template HTML 19 | dynamic JSON 20 | cached or dynamic JSON 21 | cached or dynamic JSON 22 | 23 | 24 | 25 | "naked" repo file 26 | 27 | /patch/text/plain raw diff 28 | /plain/various assets served frominside the repo directly 29 | /snapshot/archives generated from aspecific repo and ref 30 | 31 | ... all cacheable 32 | never cached 33 | 34 | ...or... 35 | /everything else/Static HTML + dynamicJSON "sandwich" 36 | 37 | 38 | -------------------------------------------------------------------------------- /etc-gitohashi/conf: -------------------------------------------------------------------------------- 1 | # these are the gitohashi server global settings 2 | # 3 | # stuff related to each vhosts should go in one 4 | # file per vhost in ./conf.d/ 5 | 6 | { 7 | "global": { 8 | "uid": "48", # after init, run as apache user 9 | "gid": "48", # after init, run as apache group 10 | "server-string": "gitohashi", 11 | "init-ssl": "yes" 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /etc-gitohashi/conf.d/localhost: -------------------------------------------------------------------------------- 1 | # you should create one file like this per vhost in /etc/gitohashi/conf.d 2 | 3 | { 4 | "vhosts": [{ 5 | # this should match the external hostname for the vhost, 6 | # like xyz.com 7 | "name": "localhost", 8 | # multiple vhosts can occupy the same port (SNI is used 9 | # to resolve) 10 | "port": "443", 11 | # required for avatar cache 12 | "enable-client-ssl": "on", 13 | # "ipv6": "on", 14 | 15 | # You should store your real certificate key somewhere safer, 16 | # eg on Fedora /etc/pki/tls/private, readable only by root. But 17 | # since the right place for this differs by distro and these 18 | # particular certs are not valuable, we get them from 19 | # /usr/local/share where they are installed. 20 | 21 | "host-ssl-key": "/usr/local/share/gitohashi/example-cert/localhost-100y.key.pem", 22 | "host-ssl-cert": "/usr/local/share/gitohashi/example-cert/localhost-100y.cert.pem", 23 | 24 | "mounts": [ 25 | { 26 | # the dynamic html + json from libjsongit2 27 | "mountpoint": "/git", 28 | "origin": "callback://gitohashi" 29 | }, 30 | { 31 | # static assets like js, css, fonts 32 | "mountpoint": "/git/_gitohashi", 33 | "origin": "file:///usr/local/share/gitohashi/assets", 34 | "cache-max-age": "7200", 35 | "cache-reuse": "1", 36 | "cache-revalidate": "0", 37 | "cache-intermediaries": "0", 38 | "extra-mimetypes": { 39 | ".zip": "application/zip", 40 | ".map": "application/json", 41 | ".ttf": "application/x-font-ttf" 42 | } 43 | }, 44 | { 45 | # semi-static cached avatar icons 46 | "mountpoint": "/git/avatar", 47 | "origin": "callback://avatar-proxy", 48 | "cache-max-age": "2400", 49 | "cache-reuse": "1", 50 | "cache-revalidate": "0", 51 | "cache-intermediaries": "0" 52 | } 53 | ], 54 | 55 | # 56 | # these headers, which will be sent on every transaction, make 57 | # recent browsers definitively ban any external script sources, 58 | # no matter what might manage to get injected later in the 59 | # page. It's a last line of defence against any successful XSS. 60 | # 61 | 62 | "headers": [{ 63 | "content-security-policy": "default-src 'none'; img-src 'self' data: https://travis-ci.org https://api.travis-ci.org https://ci.appveyor.com https://scan.coverity.com; script-src 'self'; font-src 'self'; style-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none';", 64 | "x-content-type-options": "nosniff", 65 | "x-xss-protection": "1; mode=block", 66 | "x-frame-options": "deny", 67 | "referrer-policy": "no-referrer" 68 | }], 69 | 70 | "ws-protocols": [ 71 | 72 | {"gitohashi": { 73 | # template HTML to use for this vhost. You'd normally 74 | # copy this to gitohashi-vhostname.html and modify it 75 | # to show the content, logos, links, fonts, css etc for 76 | # your vhost. It's not served directly but read from 77 | # the filesystem. Although it's cached in memory by 78 | # gitohashi, it checks for changes and reloads if 79 | # changed automatically. 80 | # 81 | # Putting logos etc as svg in css is highly recommended, 82 | # like fonts these can be transferred once with a loose 83 | # caching policy. So in practice they cost very little, 84 | # and allow the browser to compose the page without 85 | # delay. 86 | # 87 | "html-file": "/usr/local/share/gitohashi/templates/gitohashi-example.html", 88 | # 89 | # vpath required at start of links into this vhost's 90 | # gitohashi content for example if an external http 91 | # server is proxying us, and has been told to direct 92 | # URLs starting "/git" to us, this should be set to 93 | # "/git/" so URLs we generate referring to our own pages 94 | # can work. 95 | # 96 | "vpath": "/git/", 97 | # 98 | # base directory for the bare git repos we might serve 99 | # 100 | "repo-base-dir": "/srv/repositories", 101 | # 102 | # allow serving git repos that have gitolite read ACL 103 | # rights for this user. Set to "@all" to serve all 104 | # available repos (except gitolite-admin, which will 105 | # never be served) The recommended convention is a 106 | # virtual user "v-vhostname" in gitolite config and here 107 | # to mark which repos should be made available on this 108 | # vhost. 109 | # 110 | "acl-user": "@all", 111 | # 112 | # url mountpoint for the avatar cache 113 | # 114 | "avatar-url": "/git/avatar/", 115 | # 116 | # libjsgit2 JSON cache... this 117 | # should not be directly served 118 | # 119 | "cache-base": "/var/cache/libjsongit2", 120 | # 121 | # restrict the JSON cache size 122 | # to 100MB 123 | # 124 | "cache-size": "100000000", 125 | # 126 | # optional flags, b0 = 1 = blog mode 127 | # 128 | "flags": 0 129 | #"blog-repo-name": "myrepo" 130 | }, 131 | "avatar-proxy": { 132 | "remote-base": "https://www.gravatar.com/avatar/", 133 | # 134 | # this dir is served via avatar-proxy 135 | # 136 | "cache-dir": "/var/cache/libjsongit2" 137 | }} 138 | ] 139 | } 140 | ] 141 | } 142 | -------------------------------------------------------------------------------- /etc-gitohashi/conf.d/unixskt: -------------------------------------------------------------------------------- 1 | # you should create one file like this per vhost in /etc/gitohashi/conf.d 2 | 3 | { 4 | "vhosts": [{ 5 | # this has no special meaning but needs to be unique 6 | "name": "unixskt", 7 | "unix-socket": 1, 8 | # multiple vhosts can exist with different unix skt names 9 | "interface": "/var/run/gitohashi-unixskt", 10 | # required for avatar cache 11 | "enable-client-ssl": "on", 12 | 13 | "mounts": [ 14 | { 15 | # the dynamic html + json from libjsongit2 16 | "mountpoint": "/git", 17 | "origin": "callback://gitohashi" 18 | }, 19 | { 20 | # static assets like js, css, fonts 21 | "mountpoint": "/git/_gitohashi", 22 | "origin": "file:///usr/local/share/gitohashi/assets", 23 | "cache-max-age": "7200", 24 | "cache-reuse": "1", 25 | "cache-revalidate": "0", 26 | "cache-intermediaries": "0" 27 | }, 28 | { 29 | # semi-static cached avatar icons 30 | "mountpoint": "/git/avatar", 31 | "origin": "callback://avatar-proxy", 32 | "cache-max-age": "2400", 33 | "cache-reuse": "1", 34 | "cache-revalidate": "0", 35 | "cache-intermediaries": "0" 36 | } 37 | ], 38 | 39 | # 40 | # these headers, which will be sent on every transaction, make 41 | # recent browsers definitively ban any external script sources, 42 | # no matter what might manage to get injected later in the 43 | # page. It's a last line of defence against any successful XSS. 44 | # 45 | 46 | "headers": [{ 47 | "content-security-policy": "default-src 'none'; img-src 'self' data: https://travis-ci.org https://api.travis-ci.org https://ci.appveyor.com https://scan.coverity.com; script-src 'self'; font-src 'self'; style-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none';", 48 | "x-content-type-options": "nosniff", 49 | "x-xss-protection": "1; mode=block", 50 | "x-frame-options": "deny", 51 | "referrer-policy": "no-referrer" 52 | }], 53 | 54 | "ws-protocols": [ 55 | 56 | { "gitohashi": { 57 | # template HTML to use for this vhost. You'd normally 58 | # copy this to gitohashi-vhostname.html and modify it 59 | # to show the content, logos, links, fonts, css etc for 60 | # your vhost. It's not served directly but read from 61 | # the filesystem. Although it's cached in memory by 62 | # gitohashi, it checks for changes and reloads if 63 | # changed automatically. 64 | # 65 | # Putting logos etc as svg in css is highly recommended, 66 | # like fonts these can be transferred once with a loose 67 | # caching policy. So in practice they cost very little, 68 | # and allow the browser to compose the page without 69 | # delay. 70 | # 71 | "html-file": "/usr/local/share/gitohashi/templates/gitohashi-example.html", 72 | # 73 | # vpath required at start of links into this vhost's 74 | # gitohashi content for example if an external http 75 | # server is proxying us, and has been told to direct 76 | # URLs starting "/git" to us, this should be set to 77 | # "/git/" so URLs we generate referring to our own pages 78 | # can work. 79 | # 80 | "vpath": "/git/", 81 | # 82 | # base directory for the bare git repos we might serve 83 | # 84 | "repo-base-dir": "/srv/repositories", 85 | # 86 | # allow serving git repos that have gitolite read ACL 87 | # rights for this user. Set to "@all" to serve all 88 | # available repos (except gitolite-admin, which will 89 | # never be served) The recommended convention is a 90 | # virtual user "v-vhostname" in gitolite config and here 91 | # to mark which repos should be made available on this 92 | # vhost. 93 | # 94 | "acl-user": "@all", 95 | # 96 | # url mountpoint for the avatar cache 97 | # 98 | "avatar-url": "/git/avatar/", 99 | # 100 | # libjsgit2 JSON cache... this 101 | # should not be directly served 102 | # 103 | "cache-base": "/var/cache/libjsongit2", 104 | # 105 | # restrict the JSON cache size 106 | # to 100MB 107 | # 108 | "cache-size": "100000000", 109 | # 110 | # optional flags, b0 = 1 = blog mode 111 | # 112 | "flags": 0 113 | #"blog-repo-name": "myrepo" 114 | }, 115 | "avatar-proxy": { 116 | "remote-base": "https://www.gravatar.com/avatar/", 117 | # 118 | # this dir is served via avatar-proxy 119 | # 120 | "cache-dir": "/var/cache/libjsongit2" 121 | } 122 | } 123 | ] 124 | } 125 | ] 126 | } 127 | 128 | -------------------------------------------------------------------------------- /example-cert/localhost-100y.cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF5jCCA86gAwIBAgIJANq50IuwPFKgMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD 3 | VQQGEwJHQjEQMA4GA1UECAwHRXJld2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEb 4 | MBkGA1UECgwSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3Qx 5 | HzAdBgkqhkiG9w0BCQEWEG5vbmVAaW52YWxpZC5vcmcwIBcNMTgwMzIwMDQxNjA3 6 | WhgPMjExODAyMjQwNDE2MDdaMIGGMQswCQYDVQQGEwJHQjEQMA4GA1UECAwHRXJl 7 | d2hvbjETMBEGA1UEBwwKQWxsIGFyb3VuZDEbMBkGA1UECgwSbGlid2Vic29ja2V0 8 | cy10ZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHzAdBgkqhkiG9w0BCQEWEG5vbmVA 9 | aW52YWxpZC5vcmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCjYtuW 10 | aICCY0tJPubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8 11 | Di3DAmHKnSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTek 12 | LWcfI5ZZtoGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnH 13 | KT/m6DSU0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6 14 | jzhNyMBTJ1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQ 15 | Ujy5N8pSNp7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAz 16 | TK4l2pHNuC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBK 17 | Izv9cgi9fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0 18 | nPN1IMSnzXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzo 19 | GMTvP/AuehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9p 20 | sNcjTMaBQLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABo1MwUTAdBgNVHQ4EFgQU 21 | 9mYU23tW2zsomkKTAXarjr2vjuswHwYDVR0jBBgwFoAU9mYU23tW2zsomkKTAXar 22 | jr2vjuswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEANjIBMrow 23 | YNCbhAJdP7dhlhT2RUFRdeRUJD0IxrH/hkvb6myHHnK8nOYezFPjUlmRKUgNEDuA 24 | xbnXZzPdCRNV9V2mShbXvCyiDY7WCQE2Bn44z26O0uWVk+7DNNLH9BnkwUtOnM9P 25 | wtmD9phWexm4q2GnTsiL6Ul6cy0QlTJWKVLEUQQ6yda582e23J1AXqtqFcpfoE34 26 | H3afEiGy882b+ZBiwkeV+oq6XVF8sFyr9zYrv9CvWTYlkpTQfLTZSsgPdEHYVcjv 27 | xQ2D+XyDR0aRLRlvxUa9dHGFHLICG34Juq5Ai6lM1EsoD8HSsJpMcmrH7MWw2cKk 28 | ujC3rMdFTtte83wF1uuF4FjUC72+SmcQN7A386BC/nk2TTsJawTDzqwOu/VdZv2g 29 | 1WpTHlumlClZeP+G/jkSyDwqNnTu1aodDmUa4xZodfhP1HWPwUKFcq8oQr148QYA 30 | AOlbUOJQU7QwRWd1VbnwhDtQWXC92A2w1n/xkZSR1BM/NUSDhkBSUU1WjMbWg6Gg 31 | mnIZLRerQCu1Oozr87rOQqQakPkyt8BUSNK3K42j2qcfhAONdRl8Hq8Qs5pupy+s 32 | 8sdCGDlwR3JNCMv6u48OK87F4mcIxhkSefFJUFII25pCGN5WtE4p5l+9cnO1GrIX 33 | e2Hl/7M0c/lbZ4FvXgARlex2rkgS0Ka06HE= 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /example-cert/localhost-100y.key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCjYtuWaICCY0tJ 3 | PubxpIgIL+WWmz/fmK8IQr11Wtee6/IUyUlo5I602mq1qcLhT/kmpoR8Di3DAmHK 4 | nSWdPWtn1BtXLErLlUiHgZDrZWInmEBjKM1DZf+CvNGZ+EzPgBv5nTekLWcfI5ZZ 5 | toGuIP1Dl/IkNDw8zFz4cpiMe/BFGemyxdHhLrKHSm8Eo+nT734tItnHKT/m6DSU 6 | 0xlZ13d6ehLRm7/+Nx47M3XMTRH5qKP/7TTE2s0U6+M0tsGI2zpRi+m6jzhNyMBT 7 | J1u58qAe3ZW5/+YAiuZYAB6n5bhUp4oFuB5wYbcBywVR8ujInpF8buWQUjy5N8pS 8 | Np7szdYsnLJpvAd0sibrNPjC0FQCNrpNjgJmIK3+mKk4kXX7ZTwefoAzTK4l2pHN 9 | uC53QVc/EF++GBLAxmvCDq9ZpMIYi7OmzkkAKKC9Ue6Ef217LFQCFIBKIzv9cgi9 10 | fwPMLhrKleoVRNsecBsCP569WgJXhUnwf2lon4fEZr3+vRuc9shfqnV0nPN1IMSn 11 | zXCast7I2fiuRXdIz96KjlGQpP4XfNVA+RGL7aMnWOFIaVrKWLzAtgzoGMTvP/Au 12 | ehKXncBJhYtW0ltTioVx+5yTYSAZWl+IssmXjefxJqYi2/7QWmv1QC9psNcjTMaB 13 | QLN03T1Qelbs7Y27sxdEnNUth4kI+wIDAQABAoICAFWe8MQZb37k2gdAV3Y6aq8f 14 | qokKQqbCNLd3giGFwYkezHXoJfg6Di7oZxNcKyw35LFEghkgtQqErQqo35VPIoH+ 15 | vXUpWOjnCmM4muFA9/cX6mYMc8TmJsg0ewLdBCOZVw+wPABlaqz+0UOiSMMftpk9 16 | fz9JwGd8ERyBsT+tk3Qi6D0vPZVsC1KqxxL/cwIFd3Hf2ZBtJXe0KBn1pktWht5A 17 | Kqx9mld2Ovl7NjgiC1Fx9r+fZw/iOabFFwQA4dr+R8mEMK/7bd4VXfQ1o/QGGbMT 18 | G+ulFrsiDyP+rBIAaGC0i7gDjLAIBQeDhP409ZhswIEc/GBtODU372a2CQK/u4Q/ 19 | HBQvuBtKFNkGUooLgCCbFxzgNUGc83GB/6IwbEM7R5uXqsFiE71LpmroDyjKTlQ8 20 | YZkpIcLNVLw0usoGYHFm2rvCyEVlfsE3Ub8cFyTFk50SeOcF2QL2xzKmmbZEpXgl 21 | xBHR0hjgon0IKJDGfor4bHO7Nt+1Ece8u2oTEKvpz5aIn44OeC5mApRGy83/0bvs 22 | esnWjDE/bGpoT8qFuy+0urDEPNId44XcJm1IRIlG56ErxC3l0s11wrIpTmXXckqw 23 | zFR9s2z7f0zjeyxqZg4NTPI7wkM3M8BXlvp2GTBIeoxrWB4V3YArwu8QF80QBgVz 24 | mgHl24nTg00UH1OjZsABAoIBAQDOxftSDbSqGytcWqPYP3SZHAWDA0O4ACEM+eCw 25 | au9ASutl0IDlNDMJ8nC2ph25BMe5hHDWp2cGQJog7pZ/3qQogQho2gUniKDifN77 26 | 40QdykllTzTVROqmP8+efreIvqlzHmuqaGfGs5oTkZaWj5su+B+bT+9rIwZcwfs5 27 | YRINhQRx17qa++xh5mfE25c+M9fiIBTiNSo4lTxWMBShnK8xrGaMEmN7W0qTMbFH 28 | PgQz5FcxRjCCqwHilwNBeLDTp/ZECEB7y34khVh531mBE2mNzSVIQcGZP1I/DvXj 29 | W7UUNdgFwii/GW+6M0uUDy23UVQpbFzcV8o1C2nZc4Fb4zwBAoIBAQDKSJkFwwuR 30 | naVJS6WxOKjX8MCu9/cKPnwBv2mmI2jgGxHTw5sr3ahmF5eTb8Zo19BowytN+tr6 31 | 2ZFoIBA9Ubc9esEAU8l3fggdfM82cuR9sGcfQVoCh8tMg6BP8IBLOmbSUhN3PG2m 32 | 39I802u0fFNVQCJKhx1m1MFFLOu7lVcDS9JN+oYVPb6MDfBLm5jOiPuYkFZ4gH79 33 | J7gXI0/YKhaJ7yXthYVkdrSF6Eooer4RZgma62Dd1VNzSq3JBo6rYjF7Lvd+RwDC 34 | R1thHrmf/IXplxpNVkoMVxtzbrrbgnC25QmvRYc0rlS/kvM4yQhMH3eA7IycDZMp 35 | Y+0xm7I7jTT7AoIBAGKzKIMDXdCxBWKhNYJ8z7hiItNl1IZZMW2TPUiY0rl6yaCh 36 | BVXjM9W0r07QPnHZsUiByqb743adkbTUjmxdJzjaVtxN7ZXwZvOVrY7I7fPWYnCE 37 | fXCr4+IVpZI/ZHZWpGX6CGSgT6EOjCZ5IUufIvEpqVSmtF8MqfXO9o9uIYLokrWQ 38 | x1dBl5UnuTLDqw8bChq7O5y6yfuWaOWvL7nxI8NvSsfj4y635gIa/0dFeBYZEfHI 39 | UlGdNVomwXwYEzgE/c19ruIowX7HU/NgxMWTMZhpazlxgesXybel+YNcfDQ4e3RM 40 | OMz3ZFiaMaJsGGNf4++d9TmMgk4Ns6oDs6Tb9AECggEBAJYzd+SOYo26iBu3nw3L 41 | 65uEeh6xou8pXH0Tu4gQrPQTRZZ/nT3iNgOwqu1gRuxcq7TOjt41UdqIKO8vN7/A 42 | aJavCpaKoIMowy/aGCbvAvjNPpU3unU8jdl/t08EXs79S5IKPcgAx87sTTi7KDN5 43 | SYt4tr2uPEe53NTXuSatilG5QCyExIELOuzWAMKzg7CAiIlNS9foWeLyVkBgCQ6S 44 | me/L8ta+mUDy37K6vC34jh9vK9yrwF6X44ItRoOJafCaVfGI+175q/eWcqTX4q+I 45 | G4tKls4sL4mgOJLq+ra50aYMxbcuommctPMXU6CrrYyQpPTHMNVDQy2ttFdsq9iK 46 | TncCggEBAMmt/8yvPflS+xv3kg/ZBvR9JB1In2n3rUCYYD47ReKFqJ03Vmq5C9nY 47 | 56s9w7OUO8perBXlJYmKZQhO4293lvxZD2Iq4NcZbVSCMoHAUzhzY3brdgtSIxa2 48 | gGveGAezZ38qKIU26dkz7deECY4vrsRkwhpTW0LGVCpjcQoaKvymAoCmAs8V2oMr 49 | Ziw1YQ9uOUoWwOqm1wZqmVcOXvPIS2gWAs3fQlWjH9hkcQTMsUaXQDOD0aqkSY3E 50 | NqOvbCV1/oUpRi3076khCoAXI1bKSn/AvR3KDP14B5toHI/F5OTSEiGhhHesgRrs 51 | fBrpEY1IATtPq1taBZZogRqI3rOkkPk= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /examples/minimal/README.md: -------------------------------------------------------------------------------- 1 | ## Example commandline app 2 | 3 | This commandline app takes two args 4 | 5 | - a directory where bare git repositories exist inside 6 | 7 | - a "url path" like /git/myrepo to specify what JSON is 8 | needed. 9 | 10 | ## Build 11 | 12 | It's built along with the library 13 | 14 | ## Example usage 15 | 16 | ``` 17 | $ jg2-example /srv/repositories /git/myrepo 18 | $ jg2-example /srv/repositories /git/myrepo?h=mybranch 19 | $ jg2-example /srv/repositories /git/myrepo/commit?id= 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /examples/minimal/jg2-example.c: -------------------------------------------------------------------------------- 1 | /* 2 | * jg2-example.c: minimal example for using libjsongit2 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This file is made available under the Creative Commons CC0 1.0 7 | * Universal Public Domain Dedication. 8 | * 9 | * The library is LGPL 2.1... this example is CC0 to ease getting started 10 | * with your own code using the library. 11 | * 12 | * You run it from the commandline with a fake "url", against a bare git dir, 13 | * and it spits out the corresponding JSON. You use it like this 14 | * 15 | * - repo base dir 16 | * - "url" part to give the library 17 | * 18 | * This example just sets the virtual url part to "/git", this is pass in the 19 | * json to whatever will create links. The "/git" part is snipped before 20 | * passing it to the library. 21 | * 22 | * jg2-example /srv/repositores /git/myrepo 23 | * jg2-example /srv/repositores /git/myrepo/commit?id=somehash 24 | * jg2-example /srv/repositores /git/myrepo/log?h=mybranch 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #define URL_VIRTUAL_PART "/git" 34 | 35 | int 36 | main(int argc, char *argv[]) 37 | { 38 | struct jg2_ctx_create_args args; 39 | struct jg2_vhost_config config; 40 | struct jg2_vhost *vh; 41 | struct jg2_ctx *ctx; 42 | const char *mimetype; 43 | unsigned long length; 44 | int done, err = 1; 45 | char etag[36]; 46 | 47 | if (argc < 3 || strlen(argv[2]) < strlen(URL_VIRTUAL_PART)) { 48 | fprintf(stderr, "Usage: %s " 49 | " <\"/git/... url path\">\n", 50 | argv[0]); 51 | 52 | return 1; 53 | } 54 | 55 | memset(&config, 0, sizeof(config)); 56 | 57 | /* 58 | * this is the "virtual" part of the URLs, we just fix it to "git" 59 | * for the sake of a simple example. 60 | */ 61 | config.virtual_base_urlpath = "/git"; 62 | 63 | /* 64 | * this is the directory where the bare repos are living, 65 | * eg, /srv/repositories or whatever. The repository name 66 | * from the URL is added to this to find out the actual repo to 67 | * open. We pass it in the first arg 68 | */ 69 | config.repo_base_dir = argv[1]; 70 | 71 | /* 72 | * typically the vhost has its own "user" for gitolite ACL matching. 73 | * 74 | * For the demo we just allow it to see all repos with this special 75 | * macro name. 76 | */ 77 | config.acl_user = "@all"; 78 | 79 | /* 80 | * the jg2 vhost holds things like different repo objects, information 81 | * from the config set above, and universal cache for email + md5s. In 82 | * a more complex usage, it exists for longer than one context lifetime. 83 | */ 84 | vh = jg2_vhost_create(&config); 85 | if (!vh) { 86 | fprintf(stderr, "failed to open vh\n"); 87 | 88 | return 2; 89 | } 90 | 91 | /* 92 | * We create a jg2 context in the jg2 vhost, using the "url" 93 | * from the commandline like "/git/myrepo". Its lifetime is until 94 | * we finished generating that specific JSON. 95 | * 96 | * We snip the "/git" part simulating how a web server would have 97 | * removed the virtual part of the URL 98 | */ 99 | 100 | /* 101 | * This makes your code more forward-compatible, since NULL / 0 is 102 | * almost always meaning "not used" or "default" for members introduced 103 | * in later versions than your code was written with. 104 | */ 105 | memset(&args, 0, sizeof(args)); 106 | 107 | args.repo_path = (const char *)argv[2] + strlen(URL_VIRTUAL_PART); 108 | args.mimetype = &mimetype; 109 | args.length = &length; 110 | args.etag = etag; 111 | args.etag_length = sizeof(etag); 112 | 113 | if (jg2_ctx_create(vh, &ctx, &args)) { 114 | fprintf(stderr, "failed to open ctx for %s\n", argv[2]); 115 | 116 | goto bail; 117 | } 118 | 119 | do { 120 | char buf[4096]; 121 | size_t used; 122 | 123 | /* the context is stateful, we don't have to do this all at 124 | * once and we can use a different buffer every time if we 125 | * want. 126 | */ 127 | 128 | done = jg2_ctx_fill(ctx, buf, sizeof(buf), &used, NULL); 129 | if (done < 0) { 130 | fprintf(stderr, "json job failed\n"); 131 | 132 | goto bail1; 133 | } 134 | 135 | /* issue on stdout */ 136 | write(1, buf, used); 137 | 138 | } while (!done); 139 | 140 | err = 0; 141 | 142 | bail1: 143 | jg2_ctx_destroy(ctx); 144 | 145 | bail: 146 | jg2_vhost_destroy(vh); 147 | 148 | return err; 149 | } 150 | -------------------------------------------------------------------------------- /examples/threadchurn/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/warmcat/gitohashi/14d9f49d7480a8bc882284f0c0cdfa6815b6a25d/examples/threadchurn/README.md -------------------------------------------------------------------------------- /examples/threadchurn/threadchurn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * threadchurn.c: test app for threadsafe api access 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This file is made available under the Creative Commons CC0 1.0 7 | * Universal Public Domain Dedication. 8 | * 9 | * The library is LGPL 2.1... this example is CC0 to ease getting started 10 | * with your own code using the library. 11 | * 12 | * You run it from the commandline with a fake "url", against a bare git dir, 13 | * and it spits out the corresponding JSON. You use it like this 14 | * 15 | * - repo base dir 16 | * - "url" part to give the library 17 | * 18 | * This example just sets the virtual url part to "/git", this is pass in the 19 | * json to whatever will create links. The "/git" part is snipped before 20 | * passing it to the library. 21 | * 22 | * jg2-threadchurn /srv/repositores /git/myrepo 23 | * 24 | * The example spawns 8 threads each doing 1000 fetches from the urlpath 25 | * concurrently using the same vhost. 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #define URL_VIRTUAL_PART "/git" 37 | 38 | static struct jg2_vhost *vh; 39 | static int count_threads = 16; 40 | 41 | static void * 42 | thread_spam(void *d) 43 | { 44 | struct jg2_ctx_create_args args; 45 | char **argv = (char **)d; 46 | const char *mimetype; 47 | unsigned long length; 48 | struct jg2_ctx *ctx; 49 | int budget = 10000, did = 0, tried = 0; 50 | char etag[36]; 51 | int done; 52 | 53 | do { 54 | memset(&args, 0, sizeof(args)); 55 | 56 | args.repo_path = (const char *)argv[2] + 57 | strlen(URL_VIRTUAL_PART); 58 | args.mimetype = &mimetype; 59 | args.length = &length; 60 | args.etag = etag; 61 | args.etag_length = sizeof(etag); 62 | 63 | if (jg2_ctx_create(vh, &ctx, &args)) { 64 | fprintf(stderr, "failed to open ctx for %s\n", argv[2]); 65 | 66 | break; 67 | } 68 | 69 | do { 70 | char buf[4096]; 71 | size_t used; 72 | 73 | done = jg2_ctx_fill(ctx, buf, sizeof(buf), &used, NULL); 74 | if (done < 0) { 75 | fprintf(stderr, "json job failed\n"); 76 | 77 | break; 78 | } 79 | 80 | /* issue on stdout */ 81 | write(1, buf, used); 82 | 83 | } while (!done); 84 | 85 | if (done > 0) 86 | did++; 87 | 88 | jg2_ctx_destroy(ctx); 89 | 90 | 91 | } while (++tried < budget); 92 | 93 | fprintf(stderr, "thread %p: did %d / %d\n", (void *)pthread_self(), 94 | did, budget); 95 | 96 | pthread_exit(NULL); 97 | 98 | return NULL; 99 | } 100 | 101 | int 102 | main(int argc, char *argv[]) 103 | { 104 | struct jg2_vhost_config config; 105 | pthread_t pts[16]; 106 | int n; 107 | void *retval; 108 | 109 | if (argc < 3 || strlen(argv[2]) < strlen(URL_VIRTUAL_PART)) { 110 | fprintf(stderr, "Usage: %s " 111 | " <\"/git/... url path\">\n", 112 | argv[0]); 113 | 114 | return 1; 115 | } 116 | 117 | memset(&config, 0, sizeof(config)); 118 | 119 | config.virtual_base_urlpath = "/git"; 120 | config.repo_base_dir = argv[1]; 121 | config.acl_user = "@all"; 122 | 123 | vh = jg2_vhost_create(&config); 124 | if (!vh) { 125 | fprintf(stderr, "failed to open vh\n"); 126 | 127 | return 2; 128 | } 129 | 130 | for (n = 0; n < count_threads; n++) 131 | if (pthread_create(&pts[n], NULL, thread_spam, argv)) { 132 | fprintf(stderr, "thread creation failed\n"); 133 | } 134 | 135 | /* wait for the threads to complete */ 136 | 137 | for (n = 0; n < count_threads; n++) 138 | pthread_join(pts[n], &retval); 139 | 140 | jg2_vhost_destroy(vh); 141 | 142 | return 0; 143 | } 144 | -------------------------------------------------------------------------------- /include/libjsongit2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * libjsongit2.h - Public API for JSON wrapper for libgit2 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #if !defined(__LIB_JSON_GIT2_H__b1c6cef9e87b714318802950c4e08a15ffaa6559) 23 | #define __LIB_JSON_GIT2_H__b1c6cef9e87b714318802950c4e08a15ffaa6559 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #if defined(__GNUC__) 30 | #define JG2_VISIBLE __attribute__((visibility("default"))) 31 | #else 32 | #define JG2_VISIBLE 33 | #endif 34 | 35 | struct jg2_vhost; 36 | struct jg2_ctx; 37 | 38 | typedef void * jg2_md5_context; 39 | 40 | struct jg2_vhost_config { 41 | 42 | /* mandatory */ 43 | 44 | const char *virtual_base_urlpath; /**< Mandatory; like "/git" */ 45 | const char *repo_base_dir; /**< Mandatory; like "/srv/repositories" */ 46 | 47 | /* optional */ 48 | 49 | const char *acl_user; /**< like "v-warmcat"... this user is checked for 50 | READ access to allow showing repos on this 51 | vhost... context may supply an authorized 52 | user additionally that is additively checked. 53 | NULL if not used. */ 54 | 55 | const char *vhost_html_filepath; /**< like "/etc/jsongit2/vh1.html" 56 | this is optional if you don't want JSON sandwiched in HTML */ 57 | const char *avatar_url; /**< NULL for "//www.gravatar.com/avatar/", 58 | else your server / proxy cache base URL */ 59 | const char *json_cache_base; /**< directory to place JSON cache, 60 | * /var/cache/libjsongit2 recommended; 61 | * disabled if left NULL */ 62 | 63 | uint64_t cache_size_limit; /**< goal for max cache size in bytes, 64 | * 0 means use default of 256MiB */ 65 | 66 | uid_t cache_uid; /**< if you create the vhosts while still being root 67 | * and later change uid + gid, you can set the uid 68 | * of the cache directory permissions here, even if the 69 | * cache dir is somewhere privileged like /var/cache/... 70 | * The mode of the cache directory and its subdirecties 71 | * is set to 0700. Leave at 0 if you set the cache dirs 72 | * up externally */ 73 | 74 | int email_hash_bins; /**< email cache hash bins (0 defaults to 16) */ 75 | int email_hash_depth; /**< max emails per hash bin (0 defaults to 16) */ 76 | 77 | void *avatar_arg; /**< opaque pointer passed to avatar callback, if set */ 78 | 79 | #define JG2_VHOST_BLOG_MODE 1 80 | unsigned int flags; /* OR-ed flags: JG2_VHOST_BLOG_MODE = "blog mode" */ 81 | const char *blog_repo_name; /**< the repo name of the blog, if blog mode */ 82 | 83 | /* optional md5 acceleration */ 84 | 85 | jg2_md5_context (*md5_alloc)(void); 86 | /**< user code can provide accelerated md5, default of NULL means 87 | use internal implementation. All four callbacks must be given 88 | if md5_alloc() is set. Note that md5_fini does NOT free() the 89 | pointer returned by md5_alloc(), since often you want to 90 | allocate once and reuse. */ 91 | void (*md5_init)(jg2_md5_context _ctx); 92 | /**< callback prepares \p _ctx to perform a new hash */ 93 | int (*md5_upd)(jg2_md5_context _ctx, const unsigned char *input, 94 | size_t ilen); 95 | /**< callback adds \p ilen bytes from \p input to the hash */ 96 | int (*md5_fini)(jg2_md5_context _ctx, unsigned char output[16]); 97 | /**< copies the final hash to \p output. \p _ctx must be freed by the 98 | * user afterwards (or reused after md5_init() on it again) */ 99 | 100 | /* optional event callbacks */ 101 | 102 | void (*refchange)(void *user); 103 | /**< user handler for contexts who repo reflist has changed */ 104 | int (*avatar)(void *avatar_arg, const unsigned char *md5); 105 | /**< optional hook called when an avatar md5 was computed... eg 106 | * can be used to prime a side-cache with the avatar image 107 | */ 108 | }; 109 | 110 | struct jg2_ctx_create_args { 111 | const char *repo_path; /**< filesystem path to the repo */ 112 | #define JG2_CTX_FLAG_HTML 1 113 | #define JG2_CTX_FLAG_BOT 2 114 | int flags; /**< bitwise-ORed flags: JG2_CTX_FLAG_HTML = generate HTML 115 | around the JSON, using the vhost HTML file; 116 | absent = pure JSON, JG2_CTX_FLAG_BOT = don't create 117 | cache entries for this context */ 118 | const char **mimetype; /**< pointer to const char * to take pointer 119 | to mimetype */ 120 | unsigned long *length; /**< pointer to unsigned long to take 0 or 121 | length of http data if known */ 122 | char *etag; /**< pointer to char buffer to take Etag token part */ 123 | size_t etag_length; /**< length of etag char buffer */ 124 | const char *client_etag; /** NULL, or etag the client offered */ 125 | const char *authorized; /**< NULL or gitolite name for ACL use */ 126 | const char *accept_language; /**< client's accept-language hdr if any */ 127 | void *user; /**< opaque user pointer to attach to ctx */ 128 | }; 129 | 130 | /** 131 | * jg2_library_init() - "vhost" library init 132 | * 133 | * \param config: config struct 134 | * 135 | * Returns the allocated jg2 "vhost" pointer, or NULL if failed. 136 | * 137 | * The user code must call this at least once and pass the returned pointer 138 | * when creating the contexts. This call serves two purposes... first to 139 | * hide the required call to init git2 library, and second to hold sticky, 140 | * pooled data like the repo current refs and email md5 cache that contexts 141 | * can share. 142 | * 143 | * It's OK to call this more than once, for example once per web server vhost 144 | * that will be using this api. But it must be called at least once. 145 | */ 146 | JG2_VISIBLE struct jg2_vhost * 147 | jg2_vhost_create(const struct jg2_vhost_config *config); 148 | 149 | /** 150 | * jg2_library_deinit() - "vhost" deinit 151 | * 152 | * \param vhost: pointer to the vhost struct to be deallocated 153 | * 154 | * This must be called once before the process exits on every vhost pointer 155 | * allocated via jg2_vhost_create(). 156 | */ 157 | JG2_VISIBLE void 158 | jg2_vhost_destroy(struct jg2_vhost *vhost); 159 | 160 | /** 161 | * jg2_ctx_create() - create a jg2 connection context 162 | * 163 | * \param vhost: pointer to vhost library init struct context will bind to 164 | * \param ctx: pointer to the pointer that will be allocated 165 | * \param args: arguments that should be set on entry 166 | * 167 | * This creates the context for an individual connection with a urlpath. It 168 | * doesn't produce output itself, it "sets up the connection". Many connection 169 | * contexts can be ongoing simultaneously. 170 | * 171 | * Returns 0 if the context was allocated, or nonzero if it wasn't allocated 172 | * due to a problem. 173 | */ 174 | 175 | #define JG2_CTX_CREATE_ACL_DENIED 3 176 | #define JG2_CTX_CREATE_OOM 2 177 | #define JG2_CTX_CREATE_REPO_OPEN_FAIL 1 178 | 179 | JG2_VISIBLE int 180 | jg2_ctx_create(struct jg2_vhost *vhost, struct jg2_ctx **ctx, 181 | const struct jg2_ctx_create_args *args); 182 | 183 | /** 184 | * jg2_ctx_destroy() - destroy a jg2 context 185 | * 186 | * \param ctx: pointer to the pointer that will be allocated 187 | * 188 | * Deallocates anything in the context. This should be called when the 189 | * connection the context is representing is closed. 190 | */ 191 | JG2_VISIBLE int 192 | jg2_ctx_destroy(struct jg2_ctx *ctx); 193 | 194 | /** 195 | * jg2_ctx_fill() - fill a buffer with content 196 | * 197 | * \param ctx: pointer to the context 198 | * \param buf: buffer to write content into 199 | * \param len: length of buf that may be used 200 | * \param used: pointer to size_t filled with count of bytes in buf used 201 | * \param outlive: NULL or pointer to char set to 1 if the filler wishes to 202 | * outlive the wsi 203 | * 204 | * This generates the "next buffer load" of output from the connection context. 205 | * Work and anything using memory is deferred until it is actually needed, for 206 | * example trees are walked incrementally and blobs opened one at a time only 207 | * when needed for output and closed before moving on. 208 | * 209 | * If the work is bigger than one buffer it returns when the buffer is full 210 | * and resumes with a new buffer next call to jg2_ctx_fill(). 211 | * 212 | * Returns < 0 on error, or the amount of buffer bytes written 213 | */ 214 | JG2_VISIBLE int 215 | jg2_ctx_fill(struct jg2_ctx *ctx, char *buf, size_t len, size_t *used, 216 | char *outlive); 217 | 218 | #endif /* __LIB_JSON_GIT2_H__b1c6cef9e87b714318802950c4e08a15ffaa6559 */ 219 | -------------------------------------------------------------------------------- /lgtm.yml: -------------------------------------------------------------------------------- 1 | extraction: 2 | cpp: 3 | prepare: 4 | packages: 5 | - libssl-dev 6 | - libgit2-dev 7 | after_prepare: 8 | - "git clone https://libwebsockets.org/repo/libwebsockets" 9 | - "cd libwebsockets" 10 | - "mkdir build" 11 | - "cd build" 12 | - "cmake .. -DLWS_FOR_GITOHASHI=1" 13 | - "make -j4" 14 | - "cd .." 15 | - "export GNU_MAKE=make" 16 | - "export GIT=true" 17 | configure: 18 | command: 19 | - "cmake . -DGOH_LWS_INC_PATH=/opt/src/libwebsockets/build/include -DGOH_LWS_LIB_PATH=/opt/src/libwebsockets/build/lib/libwebsockets.so.15" 20 | 21 | index: 22 | build_command: 23 | - $GNU_MAKE VERBOSE=1 -j2 -s 24 | 25 | 26 | -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | ## gitohashi library sources 2 | 3 | Dir|Function 4 | ---|--- 5 | conf|Sources related to parsing configuration from git config or gitolite 6 | email|Sources related to md5 and email avatar handling 7 | job|Sources related to generating JSON for different types of URL 8 | -------------------------------------------------------------------------------- /lib/cache.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjsongit2 - cache trimming 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | * 21 | * This is a thin wrapper on the lws_diskcache_ api in lws. 22 | */ 23 | 24 | #include "private.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | 39 | /* 40 | * this lets you hash the vargs and then if non-NULL, add a suffix to the 41 | * hex md5 chars, and do the cache query flow. The suffix acts like a namespace 42 | * on the hash, since cached objects with different suffices can never collide. 43 | */ 44 | 45 | int 46 | __jg2_cache_query_v(struct jg2_ctx *ctx, int flags, const char *suffix, 47 | int *_fd, char *cache, int cache_len, const char *format, 48 | ...) 49 | { 50 | va_list ap; 51 | char buf[256], md5_hex[33], *p = md5_hex; 52 | int n, l = 0; 53 | jg2_md5_context md5_ctx; 54 | unsigned char md5[JG2_MD5_LEN]; 55 | 56 | if (suffix) 57 | l = strlen(suffix); 58 | 59 | va_start(ap, format); 60 | n = vsnprintf(buf, sizeof(buf) - l - 1, format, ap); 61 | va_end(ap); 62 | 63 | md5_ctx = ctx->vhost->cfg.md5_alloc(); 64 | if (!md5_ctx) 65 | return LWS_DISKCACHE_QUERY_NO_CACHE; 66 | ctx->vhost->cfg.md5_init(md5_ctx); 67 | ctx->vhost->cfg.md5_upd(md5_ctx, (unsigned char *)buf, n); 68 | ctx->vhost->cfg.md5_fini(md5_ctx, md5); 69 | 70 | free(md5_ctx); 71 | 72 | md5_to_hex_cstr(md5_hex, md5); 73 | 74 | if (suffix) { 75 | lws_snprintf(buf, sizeof(buf) - 1, "%s-%s", md5_hex, suffix); 76 | p = buf; 77 | } 78 | 79 | ctx->existing_cache_pos = 0; 80 | return lws_diskcache_query(ctx->vhost->cachedir->dcs, flags, p, _fd, 81 | cache, cache_len, &ctx->existing_cache_size); 82 | } 83 | 84 | 85 | 86 | /* 87 | * Check every base cache dir incrementally so it completes over 256s, one dir 88 | * for each base cache dir per second. 89 | * 90 | * The first time we see a cache dir though, do it all at once immediately. 91 | */ 92 | 93 | void * 94 | cache_trim_thread(void *d) 95 | { 96 | struct jg2_global *jg2_global = (struct jg2_global *)d; 97 | struct jg2_vhost *vh; 98 | 99 | sleep(2); /* wait for other vhosts that might set size limit */ 100 | 101 | jg2_safe_libgit2_init(); 102 | 103 | while (jg2_global->count_cachedirs) { 104 | struct jg2_repodir *rd = jg2_global->cachedir_head; 105 | 106 | while (rd) { 107 | int n, around = 1; 108 | 109 | if (!rd->subsequent) 110 | around = 256; 111 | else 112 | /* are we over the limit? Let's speed up */ 113 | if (!lws_diskcache_secs_to_idle(rd->dcs)) 114 | around = 8; 115 | 116 | for (n = 0; n < around; n++) 117 | lws_diskcache_trim(rd->dcs); 118 | 119 | rd->subsequent = 1; 120 | 121 | rd = rd->next; 122 | } 123 | 124 | sleep(1); 125 | 126 | pthread_mutex_lock(&jg2_global->lock); /* ======= global lock */ 127 | 128 | vh = jg2_global->vhost_head; 129 | while (vh) { 130 | jg2_vhost_repo_reflist_update(vh); 131 | vh = vh->vhost_list; 132 | } 133 | 134 | pthread_mutex_unlock(&jg2_global->lock); /* --- global unlock */ 135 | } 136 | 137 | jg2_safe_libgit2_deinit(); 138 | 139 | pthread_exit(NULL); 140 | 141 | return NULL; 142 | } 143 | 144 | int 145 | cache_trim_thread_spawn(struct jg2_global *jg2_global) 146 | { 147 | if (pthread_create(&jg2_global->cache_thread, NULL, 148 | cache_trim_thread, jg2_global)) 149 | return 1; 150 | 151 | #if defined(JG2_HAS_PTHREAD_SETNAME_NP) 152 | pthread_setname_np(jg2_global->cache_thread, "cache-trim"); 153 | #endif 154 | 155 | return 0; 156 | } 157 | -------------------------------------------------------------------------------- /lib/conf/gitolite/common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjsongit2 - gitolite config parsing 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "../../private.h" 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #define lp_to_rei(p, _n) lws_list_ptr_container(p, struct repo_entry_info, _n) 30 | 31 | /* repodir lock must be held */ 32 | 33 | struct repo_entry_info * 34 | __jg2_repodir_repo(struct jg2_repodir *rd, const char *repo_name) 35 | { 36 | lws_list_ptr lp = rd->rei_head; 37 | 38 | while (lp) { 39 | struct repo_entry_info *rei = lp_to_rei(lp, next); 40 | 41 | if (!strcmp((const char *)(rei + 1), repo_name)) 42 | return rei; 43 | lws_list_ptr_advance(lp); 44 | } 45 | 46 | return NULL; 47 | } 48 | 49 | /* return 0 for authorized */ 50 | 51 | int 52 | jg2_acl_check(struct jg2_ctx *ctx, const char *reponame, const char *auth) 53 | { 54 | struct jg2_vhost *vh = ctx->vhost; 55 | struct jg2_repodir *rd = vh->repodir; 56 | struct repo_entry_info *rei; 57 | int ret = 1; 58 | 59 | if (!reponame || !reponame[0]) { 60 | lwsl_err("%s: NULL or empty reponame\n", __func__); 61 | return 1; /* disallow */ 62 | } 63 | 64 | if (auth && !strcmp(auth, "@all")) 65 | return 0; /* allow everything */ 66 | 67 | pthread_mutex_lock(&rd->lock); /* ====================== repodir lock */ 68 | 69 | rei = __jg2_repodir_repo(rd, reponame); 70 | if (rei) { 71 | if (!__jg2_gitolite3_acl_check(ctx, rei, vh->cfg.acl_user)) 72 | ret = 0; 73 | else 74 | if (auth && !__jg2_gitolite3_acl_check(ctx, rei, auth)) 75 | ret = 0; 76 | } else 77 | lwsl_err("%s: no rei for %s\n", __func__, reponame); 78 | 79 | pthread_mutex_unlock(&rd->lock); /* ------------------ repodir unlock */ 80 | 81 | return ret; 82 | } 83 | 84 | int 85 | __jg2_conf_gitolite_admin_head(struct jg2_ctx *ctx) 86 | { 87 | char filepath[256], oid_hex[GIT_OID_HEXSZ + 1]; 88 | struct jg2_vhost *vh = ctx->vhost; 89 | struct jg2_repodir *rd = ctx->vhost->repodir; 90 | time_t now = time(NULL); 91 | git_repository *repo; 92 | int m, ret = 1; 93 | git_oid oid; 94 | 95 | /* don't check more often than once per second */ 96 | 97 | if (rd->last_gitolite_admin_head_check == now) 98 | return 0; 99 | 100 | /* it's OK if the gitolite-admin repo doesn't exist, we just return */ 101 | 102 | lws_snprintf(filepath, sizeof(filepath), "%s/gitolite-admin.git", 103 | vh->cfg.repo_base_dir); 104 | 105 | m = git_repository_open_ext(&repo, filepath, 0, NULL); 106 | if (m < 0) { 107 | lwsl_err("%s: git_repository_open_ext can't open %s: %d:%d uid %d gid %d\n", __func__, filepath, m, errno, (int)getuid(), (int)getgid()); 108 | goto bail; 109 | } 110 | 111 | m = git_reference_name_to_id(&oid, repo, "refs/heads/master"); 112 | if (m < 0) { 113 | lwsl_err("%s: unable to find master ref: %d\n", __func__, m); 114 | goto bail1; 115 | } 116 | 117 | oid_to_hex_cstr(oid_hex, &oid); 118 | rd->last_gitolite_admin_head_check = now; 119 | 120 | if (strcmp(oid_hex, rd->hexoid_gitolite_conf)) { 121 | 122 | /* 123 | * there has been a change in gitolite-admin, or it's the first 124 | * time we looked since starting... either way reload everything 125 | */ 126 | 127 | lwsl_notice("%s: gitolite-admin changed\n", __func__); 128 | 129 | lws_snprintf(filepath, sizeof(filepath), "/tmp/_goh_rl_%s", 130 | rd->hexoid_gitolite_conf); 131 | unlink(filepath); 132 | strcpy(rd->hexoid_gitolite_conf, oid_hex); 133 | 134 | /* 135 | * Drop ALL the rei and acl collected information 136 | * on the repodir and resets ALL the heads 137 | */ 138 | 139 | lwsac_free(&rd->rei_lwsac_head); 140 | rd->rei_head = NULL; 141 | rd->acls_known_head = NULL; 142 | 143 | /* re-acquire the basic rei list (repos in the dir) */ 144 | 145 | __jg2_conf_scan_repos(rd); 146 | } 147 | 148 | /* if we don't have it already, re-compute the vhost acl */ 149 | __jg2_conf_ensure_acl(ctx, vh->cfg.acl_user); 150 | 151 | ret = 0; 152 | 153 | bail1: 154 | git_repository_free(repo); 155 | bail: 156 | return ret; 157 | } 158 | -------------------------------------------------------------------------------- /lib/conf/private.h: -------------------------------------------------------------------------------- 1 | struct repo_entry_info; 2 | struct jg2_repodir; 3 | struct jg2_global; 4 | struct jg2_vhost; 5 | struct jg2_ctx; 6 | 7 | int 8 | jg2_conf_scan_gitolite(struct jg2_vhost *vh); 9 | 10 | int 11 | jg2_get_repo_config(git_repository *gr, struct repo_entry_info *rei, char *p); 12 | 13 | /* must have repodir lock */ 14 | int 15 | __jg2_conf_scan_repos(struct jg2_repodir *rd); 16 | 17 | int 18 | __repo_check_acl(struct jg2_vhost *vh, const char *reponame, 19 | const char *acl_name); 20 | 21 | int 22 | jg2_gitolite3_interface(struct jg2_global *jg2_global, const char *repodir); 23 | 24 | void 25 | jg2_gitolite3_interface_destroy(struct jg2_global *jg2_global); 26 | 27 | int 28 | jg2_gitolite3_blocking_query(struct jg2_global *jg2_global, const char *query, 29 | const char *stdin_path, const char *output); 30 | 31 | int 32 | __jg2_conf_ensure_acl(struct jg2_ctx *ctx, const char *acl); 33 | 34 | int 35 | __jg2_gitolite3_acl_check(struct jg2_ctx *ctx, struct repo_entry_info *rei, 36 | const char *acl); 37 | 38 | int 39 | __jg2_conf_gitolite_admin_head(struct jg2_ctx *ctx); 40 | 41 | /* repodir lock must be held */ 42 | 43 | struct repo_entry_info * 44 | __jg2_repodir_repo(struct jg2_repodir *rd, const char *reponame); 45 | 46 | int 47 | jg2_acl_check(struct jg2_ctx *ctx, const char *reponame, const char *auth); 48 | -------------------------------------------------------------------------------- /lib/conf/scan-repos.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjsongit2 - scan-repos 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "../private.h" 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define lp_to_rei(p, _n) lws_list_ptr_container(p, struct repo_entry_info, _n) 33 | 34 | static int 35 | rei_alpha_sort(lws_list_ptr a, lws_list_ptr b) 36 | { 37 | struct repo_entry_info *p1 = lp_to_rei(a, next), 38 | *p2 = lp_to_rei(b, next); 39 | 40 | return strcmp((const char *)(p1 + 1), (const char *)(p2 + 1)); 41 | } 42 | 43 | /* must have repodir lock 44 | * 45 | * We create a file /tmp/_goh_rl_GITOLITE_ADMIN_HEAD_HASH that contains a list 46 | * of dirs in the repodir (without the .git). 47 | */ 48 | 49 | int 50 | __jg2_conf_scan_repos(struct jg2_repodir *rd) 51 | { 52 | int alen, m, ret = -1, fd = -1, f = 0; 53 | char *name, filepath[256], *p; 54 | struct repo_entry_info *rei; 55 | git_repository *repo; 56 | struct dirent *de; 57 | struct stat s; 58 | DIR *dir; 59 | 60 | if (rd->rei_head) { 61 | lwsl_notice("%s: NOP since rei head populated\n", __func__); 62 | 63 | return 0; 64 | } 65 | 66 | dir = opendir(rd->repo_base_dir); 67 | if (!dir) { 68 | lwsl_err("Unable to walk repo dir '%s'\n", 69 | rd->repo_base_dir); 70 | return -1; 71 | } 72 | 73 | lws_snprintf(filepath, sizeof(filepath), 74 | "/tmp/_goh_rl_%s", rd->hexoid_gitolite_conf); 75 | fd = open(filepath, O_CREAT | O_WRONLY | O_TRUNC, 0644); 76 | if (fd < 0) { 77 | lwsl_err("%s: unable to create repo list: %s\n", __func__, filepath); 78 | ret = -1; 79 | goto bail; 80 | } 81 | 82 | lwsl_err("%s: preparing stdin %s\n", __func__, filepath); 83 | 84 | do { 85 | de = readdir(dir); 86 | if (!de) 87 | break; 88 | 89 | lws_snprintf(filepath, sizeof(filepath), "%s/%s", 90 | rd->repo_base_dir, de->d_name); 91 | 92 | if (stat(filepath, &s)) 93 | continue; 94 | 95 | if (!S_ISDIR(s.st_mode)) 96 | continue; 97 | 98 | m = strlen(de->d_name); 99 | if (m <= 4 || strcmp(de->d_name + m - 4, ".git")) 100 | continue; 101 | 102 | /* this cannot be served, so hide it */ 103 | if (!strcmp(de->d_name, "gitolite-admin.git")) 104 | continue; 105 | 106 | if (git_repository_open_ext(&repo, filepath, 0, NULL)) { 107 | if (giterr_last()) 108 | giterr_clear(); 109 | continue; 110 | } 111 | 112 | if (giterr_last()) 113 | giterr_clear(); 114 | 115 | /* just compute the size of the config elements */ 116 | alen = jg2_get_repo_config(repo, NULL, NULL); 117 | 118 | /* allocate the whole area at once */ 119 | rei = lwsac_use(&rd->rei_lwsac_head, sizeof(*rei) + m + alen, 0); 120 | if (!rei) { 121 | git_repository_free(repo); 122 | goto bail; 123 | } 124 | 125 | /* copy the name in; lac is already advanced + aligned */ 126 | p = name = (char *)(rei + 1); 127 | memcpy(name, de->d_name, m - 4); 128 | name[m - 4] = '\0'; 129 | 130 | lwsl_err("%s: creating rei for %s\n", __func__, name); 131 | 132 | p += m - 3; 133 | rei->name_len = m - 3; 134 | rei->acls_valid_head = NULL; 135 | rei->conf_len[0] = rei->conf_len[1] = rei->conf_len[2] = 0; 136 | 137 | /* place the config elements */ 138 | jg2_get_repo_config(repo, rei, p); 139 | 140 | lws_list_ptr_insert(&rd->rei_head, &rei->next, rei_alpha_sort); 141 | 142 | git_repository_free(repo); 143 | 144 | if (f) 145 | write(fd, "\n", 1); 146 | f = 1; 147 | write(fd, de->d_name, m - 4); 148 | 149 | } while (de); 150 | 151 | ret = 0; 152 | 153 | bail: 154 | if (fd != -1) 155 | close(fd); 156 | closedir(dir); 157 | 158 | return ret; 159 | } 160 | -------------------------------------------------------------------------------- /lib/email/README.md: -------------------------------------------------------------------------------- 1 | ## Email md5 cache 2 | 3 | The vhost keeps a cache of computed md5s for a certain amount of recently-seen 4 | email addresses, for use with an avatar provider like gravatar. 5 | 6 | By default it's 16 hash bins each of depth 16, so the most recent 256 emails 7 | the vhost has seen. 8 | 9 | Each time an email is referenced, it is moved to the start of its hash bin; 10 | when the bins are full the least-recently seen email is replaced with a new 11 | reference. 12 | -------------------------------------------------------------------------------- /lib/email/email.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjg2 - email / md5 hashing 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "../private.h" 23 | #include 24 | 25 | #include 26 | 27 | static int 28 | __email_hash(struct jg2_vhost *vh, const char *email) 29 | { 30 | int s = 0; 31 | 32 | while (*email) 33 | s += *email++; 34 | 35 | return s % vh->cfg.email_hash_bins; 36 | } 37 | 38 | unsigned char * 39 | email_md5(struct jg2_vhost *vh, const char *email) 40 | { 41 | struct jg2_email *p, *op = NULL, *oop = NULL, *ne; 42 | int bin; 43 | 44 | pthread_mutex_lock(&vh->lock); /* ======================== vhost lock */ 45 | 46 | bin = __email_hash(vh, email); 47 | 48 | p = vh->bins[bin].first; 49 | 50 | while (p) { 51 | if (!(strcmp(p->email, email))) { 52 | /* 53 | * every time we get a hit, move him to be "first" in 54 | * the linked-list, ie, quickest to find, and farthest 55 | * from getting replaced on the end 56 | */ 57 | 58 | if (vh->cfg.avatar) 59 | vh->cfg.avatar(vh->cfg.avatar_arg, p->md5); 60 | 61 | if (!op) { /* he is first */ 62 | pthread_mutex_unlock(&vh->lock); /* vh unlock */ 63 | return p->md5; 64 | } 65 | 66 | op->next = p->next; 67 | p->next = vh->bins[bin].first; 68 | vh->bins[bin].first = p; 69 | 70 | pthread_mutex_unlock(&vh->lock); /*----- vhost unlock */ 71 | return p->md5; 72 | } 73 | 74 | oop = op; 75 | op = p; 76 | p = p->next; 77 | } 78 | 79 | /* if the search failed, we naturally end up at the end of the ll */ 80 | 81 | if (vh->bins[bin].count < vh->cfg.email_hash_depth) { 82 | /* create a new one */ 83 | 84 | vh->bins[bin].count++; 85 | ne = malloc(sizeof(*ne)); 86 | if (!ne) { 87 | pthread_mutex_unlock(&vh->lock); /*----- vhost unlock */ 88 | return NULL; 89 | } 90 | } else 91 | /* replace the "last" one */ 92 | 93 | ne = op; 94 | 95 | ne->next = NULL; 96 | strncpy(ne->email, email, sizeof(ne->email) - 1); 97 | ne->email[sizeof(ne->email) - 1] = '\0'; 98 | 99 | if (ne == op && oop) /* replacing existing, just move to front */ 100 | oop->next = op->next; /* should always be NULL */ 101 | 102 | /* adding new at front */ 103 | ne->next = vh->bins[bin].first; 104 | vh->bins[bin].first = ne; 105 | 106 | vh->cfg.md5_init(vh->md5_ctx); 107 | vh->cfg.md5_upd(vh->md5_ctx, (unsigned char *)email, strlen(email)); 108 | vh->cfg.md5_fini(vh->md5_ctx, ne->md5); 109 | 110 | if (vh->cfg.avatar) 111 | vh->cfg.avatar(vh->cfg.avatar_arg, ne->md5); 112 | 113 | pthread_mutex_unlock(&vh->lock); /*--------------------- vhost unlock */ 114 | 115 | return ne->md5; 116 | } 117 | 118 | int 119 | email_vhost_init(struct jg2_vhost *vh) 120 | { 121 | size_t s; 122 | 123 | /* adjust defaults left at 0 */ 124 | 125 | if (!vh->cfg.email_hash_bins) 126 | vh->cfg.email_hash_bins = 16; 127 | 128 | if (!vh->cfg.email_hash_depth) 129 | vh->cfg.email_hash_depth = 16; 130 | 131 | if (!vh->cfg.md5_alloc) { 132 | /* use the inbuilt default */ 133 | vh->cfg.md5_alloc = jg2_md5_alloc; 134 | vh->cfg.md5_init = jg2_md5_init; 135 | vh->cfg.md5_upd = jg2_md5_upd; 136 | vh->cfg.md5_fini = jg2_md5_fini; 137 | } 138 | 139 | vh->md5_ctx = vh->cfg.md5_alloc(); 140 | 141 | /* allocate the hash bin array */ 142 | 143 | s = sizeof(*vh->bins) * vh->cfg.email_hash_bins; 144 | vh->bins = malloc(s); 145 | if (!vh->bins) 146 | return -1; 147 | 148 | memset(vh->bins, 0, s); 149 | 150 | return 0; 151 | } 152 | 153 | void 154 | email_vhost_deinit(struct jg2_vhost *vh) 155 | { 156 | int n; 157 | 158 | if (!vh->bins) 159 | return; 160 | 161 | for (n = 0; n < vh->cfg.email_hash_bins; n++) { 162 | struct jg2_email *p = vh->bins[n].first, *p1; 163 | 164 | while (p) { 165 | p1 = p->next; 166 | free(p); 167 | p = p1; 168 | } 169 | } 170 | 171 | free(vh->md5_ctx); 172 | 173 | free(vh->bins); 174 | } 175 | -------------------------------------------------------------------------------- /lib/email/md5.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved 3 | * SPDX-License-Identifier: Apache-2.0 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | * not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | typedef struct { 27 | uint32_t total[2]; /*!< number of bytes processed */ 28 | uint32_t state[4]; /*!< intermediate digest state */ 29 | unsigned char buffer[64]; /*!< data block being processed */ 30 | } mbedtls_md5_context; 31 | 32 | /* 33 | * The MD5 algorithm was designed by Ron Rivest in 1991. 34 | * 35 | * http://www.ietf.org/rfc/rfc1321.txt 36 | */ 37 | 38 | #define GET_UINT32_LE(n,b,i) \ 39 | { \ 40 | (n) = ( (uint32_t) (b)[(i) ] ) \ 41 | | ( (uint32_t) (b)[(i) + 1] << 8 ) \ 42 | | ( (uint32_t) (b)[(i) + 2] << 16 ) \ 43 | | ( (uint32_t) (b)[(i) + 3] << 24 ); \ 44 | } 45 | 46 | #define PUT_UINT32_LE(n,b,i) \ 47 | { \ 48 | (b)[(i) ] = (unsigned char) ( ( (n) ) & 0xFF ); \ 49 | (b)[(i) + 1] = (unsigned char) ( ( (n) >> 8 ) & 0xFF ); \ 50 | (b)[(i) + 2] = (unsigned char) ( ( (n) >> 16 ) & 0xFF ); \ 51 | (b)[(i) + 3] = (unsigned char) ( ( (n) >> 24 ) & 0xFF ); \ 52 | } 53 | 54 | static int 55 | mbedtls_internal_md5_process( mbedtls_md5_context *ctx, 56 | const unsigned char data[64] ) 57 | { 58 | uint32_t X[16], A, B, C, D; 59 | 60 | GET_UINT32_LE( X[ 0], data, 0 ); 61 | GET_UINT32_LE( X[ 1], data, 4 ); 62 | GET_UINT32_LE( X[ 2], data, 8 ); 63 | GET_UINT32_LE( X[ 3], data, 12 ); 64 | GET_UINT32_LE( X[ 4], data, 16 ); 65 | GET_UINT32_LE( X[ 5], data, 20 ); 66 | GET_UINT32_LE( X[ 6], data, 24 ); 67 | GET_UINT32_LE( X[ 7], data, 28 ); 68 | GET_UINT32_LE( X[ 8], data, 32 ); 69 | GET_UINT32_LE( X[ 9], data, 36 ); 70 | GET_UINT32_LE( X[10], data, 40 ); 71 | GET_UINT32_LE( X[11], data, 44 ); 72 | GET_UINT32_LE( X[12], data, 48 ); 73 | GET_UINT32_LE( X[13], data, 52 ); 74 | GET_UINT32_LE( X[14], data, 56 ); 75 | GET_UINT32_LE( X[15], data, 60 ); 76 | 77 | #define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) 78 | 79 | #define P(a,b,c,d,k,s,t) \ 80 | { \ 81 | a += F(b,c,d) + X[k] + t; a = S(a,s) + b; \ 82 | } 83 | 84 | A = ctx->state[0]; 85 | B = ctx->state[1]; 86 | C = ctx->state[2]; 87 | D = ctx->state[3]; 88 | 89 | #define F(x,y,z) (z ^ (x & (y ^ z))) 90 | 91 | P( A, B, C, D, 0, 7, 0xD76AA478 ); 92 | P( D, A, B, C, 1, 12, 0xE8C7B756 ); 93 | P( C, D, A, B, 2, 17, 0x242070DB ); 94 | P( B, C, D, A, 3, 22, 0xC1BDCEEE ); 95 | P( A, B, C, D, 4, 7, 0xF57C0FAF ); 96 | P( D, A, B, C, 5, 12, 0x4787C62A ); 97 | P( C, D, A, B, 6, 17, 0xA8304613 ); 98 | P( B, C, D, A, 7, 22, 0xFD469501 ); 99 | P( A, B, C, D, 8, 7, 0x698098D8 ); 100 | P( D, A, B, C, 9, 12, 0x8B44F7AF ); 101 | P( C, D, A, B, 10, 17, 0xFFFF5BB1 ); 102 | P( B, C, D, A, 11, 22, 0x895CD7BE ); 103 | P( A, B, C, D, 12, 7, 0x6B901122 ); 104 | P( D, A, B, C, 13, 12, 0xFD987193 ); 105 | P( C, D, A, B, 14, 17, 0xA679438E ); 106 | P( B, C, D, A, 15, 22, 0x49B40821 ); 107 | 108 | #undef F 109 | 110 | #define F(x,y,z) (y ^ (z & (x ^ y))) 111 | 112 | P( A, B, C, D, 1, 5, 0xF61E2562 ); 113 | P( D, A, B, C, 6, 9, 0xC040B340 ); 114 | P( C, D, A, B, 11, 14, 0x265E5A51 ); 115 | P( B, C, D, A, 0, 20, 0xE9B6C7AA ); 116 | P( A, B, C, D, 5, 5, 0xD62F105D ); 117 | P( D, A, B, C, 10, 9, 0x02441453 ); 118 | P( C, D, A, B, 15, 14, 0xD8A1E681 ); 119 | P( B, C, D, A, 4, 20, 0xE7D3FBC8 ); 120 | P( A, B, C, D, 9, 5, 0x21E1CDE6 ); 121 | P( D, A, B, C, 14, 9, 0xC33707D6 ); 122 | P( C, D, A, B, 3, 14, 0xF4D50D87 ); 123 | P( B, C, D, A, 8, 20, 0x455A14ED ); 124 | P( A, B, C, D, 13, 5, 0xA9E3E905 ); 125 | P( D, A, B, C, 2, 9, 0xFCEFA3F8 ); 126 | P( C, D, A, B, 7, 14, 0x676F02D9 ); 127 | P( B, C, D, A, 12, 20, 0x8D2A4C8A ); 128 | 129 | #undef F 130 | 131 | #define F(x,y,z) (x ^ y ^ z) 132 | 133 | P( A, B, C, D, 5, 4, 0xFFFA3942 ); 134 | P( D, A, B, C, 8, 11, 0x8771F681 ); 135 | P( C, D, A, B, 11, 16, 0x6D9D6122 ); 136 | P( B, C, D, A, 14, 23, 0xFDE5380C ); 137 | P( A, B, C, D, 1, 4, 0xA4BEEA44 ); 138 | P( D, A, B, C, 4, 11, 0x4BDECFA9 ); 139 | P( C, D, A, B, 7, 16, 0xF6BB4B60 ); 140 | P( B, C, D, A, 10, 23, 0xBEBFBC70 ); 141 | P( A, B, C, D, 13, 4, 0x289B7EC6 ); 142 | P( D, A, B, C, 0, 11, 0xEAA127FA ); 143 | P( C, D, A, B, 3, 16, 0xD4EF3085 ); 144 | P( B, C, D, A, 6, 23, 0x04881D05 ); 145 | P( A, B, C, D, 9, 4, 0xD9D4D039 ); 146 | P( D, A, B, C, 12, 11, 0xE6DB99E5 ); 147 | P( C, D, A, B, 15, 16, 0x1FA27CF8 ); 148 | P( B, C, D, A, 2, 23, 0xC4AC5665 ); 149 | 150 | #undef F 151 | 152 | #define F(x,y,z) (y ^ (x | ~z)) 153 | 154 | P( A, B, C, D, 0, 6, 0xF4292244 ); 155 | P( D, A, B, C, 7, 10, 0x432AFF97 ); 156 | P( C, D, A, B, 14, 15, 0xAB9423A7 ); 157 | P( B, C, D, A, 5, 21, 0xFC93A039 ); 158 | P( A, B, C, D, 12, 6, 0x655B59C3 ); 159 | P( D, A, B, C, 3, 10, 0x8F0CCC92 ); 160 | P( C, D, A, B, 10, 15, 0xFFEFF47D ); 161 | P( B, C, D, A, 1, 21, 0x85845DD1 ); 162 | P( A, B, C, D, 8, 6, 0x6FA87E4F ); 163 | P( D, A, B, C, 15, 10, 0xFE2CE6E0 ); 164 | P( C, D, A, B, 6, 15, 0xA3014314 ); 165 | P( B, C, D, A, 13, 21, 0x4E0811A1 ); 166 | P( A, B, C, D, 4, 6, 0xF7537E82 ); 167 | P( D, A, B, C, 11, 10, 0xBD3AF235 ); 168 | P( C, D, A, B, 2, 15, 0x2AD7D2BB ); 169 | P( B, C, D, A, 9, 21, 0xEB86D391 ); 170 | 171 | #undef F 172 | 173 | ctx->state[0] += A; 174 | ctx->state[1] += B; 175 | ctx->state[2] += C; 176 | ctx->state[3] += D; 177 | 178 | return( 0 ); 179 | } 180 | 181 | static int 182 | mbedtls_md5_update_ret( mbedtls_md5_context *ctx, 183 | const unsigned char *input, 184 | size_t ilen ) 185 | { 186 | int ret; 187 | size_t fill; 188 | uint32_t left; 189 | 190 | if( ilen == 0 ) 191 | return( 0 ); 192 | 193 | left = ctx->total[0] & 0x3F; 194 | fill = 64 - left; 195 | 196 | ctx->total[0] += (uint32_t) ilen; 197 | ctx->total[0] &= 0xFFFFFFFF; 198 | 199 | if( ctx->total[0] < (uint32_t) ilen ) 200 | ctx->total[1]++; 201 | 202 | if( left && ilen >= fill ) 203 | { 204 | memcpy( (void *) (ctx->buffer + left), input, fill ); 205 | if( ( ret = mbedtls_internal_md5_process( ctx, ctx->buffer ) ) != 0 ) 206 | return( ret ); 207 | 208 | input += fill; 209 | ilen -= fill; 210 | left = 0; 211 | } 212 | 213 | while( ilen >= 64 ) 214 | { 215 | if( ( ret = mbedtls_internal_md5_process( ctx, input ) ) != 0 ) 216 | return( ret ); 217 | 218 | input += 64; 219 | ilen -= 64; 220 | } 221 | 222 | if( ilen > 0 ) 223 | { 224 | memcpy( (void *) (ctx->buffer + left), input, ilen ); 225 | } 226 | 227 | return( 0 ); 228 | } 229 | 230 | static const unsigned char md5_padding[64] = 231 | { 232 | 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 233 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 234 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 235 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 236 | }; 237 | 238 | static int 239 | mbedtls_md5_finish_ret( mbedtls_md5_context *ctx, 240 | unsigned char output[16] ) 241 | { 242 | int ret; 243 | uint32_t last, padn; 244 | uint32_t high, low; 245 | unsigned char msglen[8]; 246 | 247 | high = ( ctx->total[0] >> 29 ) 248 | | ( ctx->total[1] << 3 ); 249 | low = ( ctx->total[0] << 3 ); 250 | 251 | PUT_UINT32_LE( low, msglen, 0 ); 252 | PUT_UINT32_LE( high, msglen, 4 ); 253 | 254 | last = ctx->total[0] & 0x3F; 255 | padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); 256 | 257 | if( ( ret = mbedtls_md5_update_ret( ctx, md5_padding, padn ) ) != 0 ) 258 | return( ret ); 259 | 260 | if( ( ret = mbedtls_md5_update_ret( ctx, msglen, 8 ) ) != 0 ) 261 | return( ret ); 262 | 263 | PUT_UINT32_LE( ctx->state[0], output, 0 ); 264 | PUT_UINT32_LE( ctx->state[1], output, 4 ); 265 | PUT_UINT32_LE( ctx->state[2], output, 8 ); 266 | PUT_UINT32_LE( ctx->state[3], output, 12 ); 267 | 268 | return( 0 ); 269 | } 270 | 271 | /* 272 | * these are the default per-vhost md5 implementation functions... they can 273 | * be overridden in jg2 config at vhost creation time. 274 | */ 275 | 276 | jg2_md5_context 277 | jg2_md5_alloc(void) 278 | { 279 | return malloc(sizeof(mbedtls_md5_context)); 280 | } 281 | 282 | void 283 | jg2_md5_init(jg2_md5_context _ctx) 284 | { 285 | mbedtls_md5_context *ctx = (mbedtls_md5_context *)_ctx; 286 | 287 | memset(ctx, 0, sizeof(*ctx)); 288 | 289 | ctx->state[0] = 0x67452301; 290 | ctx->state[1] = 0xEFCDAB89; 291 | ctx->state[2] = 0x98BADCFE; 292 | ctx->state[3] = 0x10325476; 293 | } 294 | 295 | int 296 | jg2_md5_upd(jg2_md5_context _ctx, const unsigned char *input, size_t ilen) 297 | { 298 | mbedtls_md5_context *ctx = (mbedtls_md5_context *)_ctx; 299 | 300 | return mbedtls_md5_update_ret(ctx, input, ilen); 301 | } 302 | 303 | int 304 | jg2_md5_fini(jg2_md5_context _ctx, unsigned char output[16]) 305 | { 306 | mbedtls_md5_context *ctx = (mbedtls_md5_context *)_ctx; 307 | int ret; 308 | 309 | ret = mbedtls_md5_finish_ret(ctx, output); 310 | 311 | return ret; 312 | } 313 | -------------------------------------------------------------------------------- /lib/email/private.h: -------------------------------------------------------------------------------- 1 | 2 | struct jg2_email { 3 | struct jg2_email *next; 4 | char email[64]; 5 | unsigned char md5[JG2_MD5_LEN]; 6 | }; 7 | 8 | struct jg2_email_hash_bin { 9 | struct jg2_email *first; 10 | int count; 11 | }; 12 | 13 | int 14 | email_vhost_init(struct jg2_vhost *vhost); 15 | 16 | void 17 | email_vhost_deinit(struct jg2_vhost *vhost); 18 | 19 | unsigned char * /* may return NULL on OOM */ 20 | email_md5(struct jg2_vhost *vhost, const char *email); 21 | -------------------------------------------------------------------------------- /lib/job/blog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjsongit2 - blog 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | * 21 | * If the vhost is in blog mode, then the urplaths have no repository part, 22 | * it's faked to be as set by the vhost init args, and no mode part. 23 | * 24 | * /vpath[/path] 25 | * 26 | * If there is a path part, the mode is faked to be "tree" to use the existing 27 | * inline blob capability and provide by-year and by-month "directory listings" 28 | * reusing the tree dir listing as well. 29 | * 30 | * If there's no path part, we end up here to provide a paginated overview 31 | * listing in time order. 32 | */ 33 | 34 | #include "../private.h" 35 | 36 | #include 37 | #include 38 | 39 | static int 40 | job_blog_start(struct jg2_ctx *ctx) 41 | { 42 | if (git_reference_iterator_new(&ctx->iter_ref, ctx->jrepo->repo) < 0) 43 | return -1; 44 | 45 | meta_header(ctx); 46 | 47 | job_common_header(ctx); 48 | CTX_BUF_APPEND("\"reflist\": ["); 49 | 50 | return 0; 51 | } 52 | 53 | static void 54 | job_blog_destroy(struct jg2_ctx *ctx) 55 | { 56 | if (ctx->iter_ref) { 57 | git_reference_iterator_free(ctx->iter_ref); 58 | ctx->iter_ref = NULL; 59 | } 60 | ctx->job = NULL; 61 | } 62 | 63 | int 64 | job_blog(struct jg2_ctx *ctx) 65 | { 66 | if (ctx->destroying) { 67 | job_blog_destroy(ctx); 68 | 69 | return 0; 70 | } 71 | 72 | if (!ctx->partway && job_blog_start(ctx)) 73 | return -1; 74 | 75 | while (JG2_HAS_SPACE(ctx, 768)) { 76 | char pure[128]; 77 | git_reference *gref, *rref; 78 | const git_oid *oid; 79 | 80 | /* 81 | * This will list 'commit' and 'tag' objects... 82 | * 'commit' objects named refs/heads/xxx represent branch xxx 83 | */ 84 | 85 | if (git_reference_next(&gref, ctx->iter_ref) < 0) { 86 | meta_trailer(ctx, "\n]"); 87 | job_blog_destroy(ctx); 88 | break; 89 | } 90 | 91 | git_reference_resolve(&rref, gref); 92 | oid = git_reference_target(rref); 93 | if (oid) { 94 | jg2_json_purify(pure, git_reference_name(gref), 95 | sizeof(pure), NULL); 96 | CTX_BUF_APPEND("%c\n{ \"name\": \"%s\"," 97 | "\"summary\": ", 98 | ctx->subsequent ? ',' : ' ', pure); 99 | ctx->subsequent = 1; 100 | generic_object_summary(oid, ctx); 101 | 102 | CTX_BUF_APPEND("}"); 103 | } 104 | 105 | git_reference_free(rref); 106 | git_reference_free(gref); 107 | } 108 | 109 | return 0; 110 | } 111 | -------------------------------------------------------------------------------- /lib/job/commit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjsongit2 - commit 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "../private.h" 23 | 24 | #include 25 | #include 26 | 27 | static int 28 | common_print_cb(struct jg2_ctx *ctx, int origin, 29 | const char *content, size_t content_length) 30 | { 31 | unsigned int u = content_length; 32 | char *p, do_pre = 0; 33 | 34 | switch (origin) { 35 | case GIT_DIFF_LINE_CONTEXT: 36 | case GIT_DIFF_LINE_ADDITION: 37 | case GIT_DIFF_LINE_DELETION: 38 | u++; 39 | do_pre = 1; 40 | break; 41 | default: 42 | break; 43 | } 44 | 45 | p = lwsac_use(&ctx->lwsac_head, u + 4, 0); 46 | if (!p) 47 | return -1; 48 | 49 | *p++ = u & 0xff; 50 | *p++ = (u >> 8) & 0xff; 51 | *p++ = (u >> 16) & 0xff; 52 | *p++ = (u >> 24) & 0xff; 53 | 54 | if (do_pre) { 55 | *p++ = origin; 56 | u--; 57 | } 58 | 59 | memcpy(p, content, u); 60 | 61 | return 0; 62 | } 63 | 64 | #if !LIBGIT2_HAS_DIFF 65 | /* 66 | * print it into a lac, adding chunk buffers as needed, sized as needed. 67 | */ 68 | int patch_print_cb( 69 | const git_diff_delta *delta, /** delta that contains this data */ 70 | const git_diff_range *range, /** range of lines containing this data */ 71 | char line_origin, /** git_diff_list_t value from above */ 72 | const char *content, /** diff data - not NUL terminated */ 73 | size_t content_len, /** number of bytes of diff data */ 74 | void *payload) 75 | { 76 | struct jg2_ctx *ctx = (struct jg2_ctx *)payload; 77 | 78 | return common_print_cb(ctx, line_origin, content, content_len); 79 | } 80 | #else 81 | /* 82 | * callback interface is different at v0.24 83 | */ 84 | int patch_print_cb( 85 | const git_diff_delta *delta, /** delta that contains this data */ 86 | const git_diff_hunk *hunk, /**< hunk containing this data */ 87 | const git_diff_line *line, /**< line data */ 88 | void *payload) 89 | { 90 | struct jg2_ctx *ctx = (struct jg2_ctx *)payload; 91 | 92 | return common_print_cb(ctx, line->origin, line->content, 93 | line->content_len); 94 | } 95 | #endif 96 | 97 | static int 98 | job_commit_start(struct jg2_ctx *ctx) 99 | { 100 | git_tree *tp = NULL, *t = NULL; 101 | git_commit *parent = NULL; 102 | #if !LIBGIT2_HAS_DIFF 103 | git_diff_list *d = NULL; 104 | #else 105 | git_diff *d = NULL; 106 | #endif 107 | git_generic_ptr u; 108 | git_oid oid; 109 | int e, ret = 1; 110 | 111 | if (!ctx->hex_oid[0]) { 112 | lwsl_err("%s: no oid\n", __func__); 113 | return 1; 114 | } 115 | 116 | e = jg2_oid_lookup(ctx->jrepo->repo, &oid, ctx->hex_oid); 117 | if (e) 118 | return e; 119 | 120 | e = git_object_lookup(&u.obj, ctx->jrepo->repo, &oid, GIT_OBJ_ANY); 121 | if (e < 0) { 122 | lwsl_err("%s: git_object_lookup '%s': %d\n", __func__, 123 | ctx->hex_oid, e); 124 | 125 | return -1; 126 | } 127 | 128 | if (git_object_type(u.obj) != GIT_OBJ_COMMIT) { 129 | lwsl_err("%s: not commit '%s'\n", __func__, ctx->hex_oid); 130 | git_object_free(u.obj); 131 | 132 | return -1; 133 | } 134 | 135 | ctx->raw_patch = !strcmp(ctx->sr.e[JG2_PE_MODE], "patch"); 136 | 137 | ctx->u = u; 138 | if (!ctx->raw_patch) { 139 | meta_header(ctx); 140 | 141 | /* we issue three phases... 1) for html,the generic commit summary... */ 142 | 143 | job_common_header(ctx); 144 | 145 | CTX_BUF_APPEND("\"commit\": {"); 146 | 147 | commit_summary(ctx->u.commit, ctx); 148 | } else { 149 | 150 | /* ... for raw patch, synthesized header info */ 151 | 152 | signature_text(git_commit_author(ctx->u.commit), ctx); 153 | CTX_BUF_APPEND("\n"); 154 | } 155 | 156 | /* 2) if any, the commit message body (part after the first paragraph */ 157 | 158 | ctx->body = git_commit_message(ctx->u.commit); 159 | if (!ctx->raw_patch && ctx->body) 160 | CTX_BUF_APPEND("},\n \"body\": \""); 161 | 162 | /* 3) if any, the diff itself */ 163 | 164 | ctx->pos = 0; 165 | 166 | e = git_commit_parent(&parent, ctx->u.commit, 0); 167 | if (!e) { 168 | e = git_commit_tree(&tp, parent); 169 | if (e < 0) { 170 | lwsl_err("%s: git_commit_tree (parent) failed %d\n", 171 | __func__, e); 172 | goto bail2; 173 | } 174 | } else 175 | tp = NULL; 176 | 177 | e = git_commit_tree(&t, ctx->u.commit); 178 | if (e < 0) { 179 | lwsl_err("%s: git_commit_tree failed %d\n", __func__, e); 180 | goto bail1; 181 | } 182 | 183 | e = git_diff_tree_to_tree(&d, ctx->jrepo->repo, tp, t, NULL); 184 | if (e < 0) { 185 | lwsl_err("%s: git_diff_tree_to_tree failed %d\n", __func__, e); 186 | goto bail1; 187 | } 188 | 189 | #if LIBGIT2_HAS_DIFF 190 | if (git_diff_print(d, GIT_DIFF_FORMAT_PATCH, patch_print_cb, ctx) < 0) 191 | { 192 | lwsl_err("%s: git_diff_print failed\n", __func__); 193 | goto bail; 194 | } 195 | #else /* v0.19.0 */ 196 | if (git_diff_print_patch(d, patch_print_cb, ctx) < 0) 197 | { 198 | lwsl_err("%s: git_diff_print_patch failed\n", __func__); 199 | goto bail; 200 | } 201 | #endif 202 | ctx->lac = ctx->lwsac_head; 203 | ctx->pos = 0; 204 | ctx->size = 0; 205 | ctx->ofs = lwsac_sizeof(1); 206 | 207 | if (!ctx->raw_patch && !ctx->body) 208 | CTX_BUF_APPEND("},\n \"diff\": \""); 209 | 210 | ret = 0; 211 | 212 | bail: 213 | #if LIBGIT2_HAS_DIFF 214 | git_diff_free(d); 215 | #else 216 | git_diff_list_free(d); 217 | #endif 218 | bail1: 219 | if (tp) 220 | git_tree_free(tp); 221 | bail2: 222 | if (t) 223 | git_tree_free(t); 224 | if (parent) 225 | git_commit_free(parent); 226 | 227 | if (ret) 228 | lwsl_err("%s: failed out\n", __func__); 229 | 230 | return ret; 231 | } 232 | 233 | static void 234 | job_commit_destroy(struct jg2_ctx *ctx) 235 | { 236 | if (ctx->u.commit) { 237 | git_commit_free(ctx->u.commit); 238 | ctx->u.commit = NULL; 239 | } 240 | 241 | lwsac_free(&ctx->lwsac_head); 242 | 243 | ctx->job = NULL; 244 | } 245 | 246 | int 247 | job_commit(struct jg2_ctx *ctx) 248 | { 249 | int m; 250 | 251 | if (ctx->destroying) { 252 | job_commit_destroy(ctx); 253 | 254 | return 0; 255 | } 256 | 257 | if (!ctx->partway && job_commit_start(ctx)) { 258 | lwsl_err("%s: start failed (%s)\n", __func__, ctx->hex_oid); 259 | return -1; 260 | } 261 | 262 | /* add body */ 263 | 264 | while (ctx->body && JG2_HAS_SPACE(ctx, 768)) { 265 | size_t inlim_totlen = 0; 266 | 267 | if (!ctx->body) 268 | goto ended; 269 | 270 | /* control chars may bloat to 6x */ 271 | while (inlim_totlen < 84 && ctx->body[inlim_totlen]) 272 | inlim_totlen++; 273 | 274 | m = lws_ptr_diff(ctx->end, ctx->p) - 1; 275 | if (!ctx->raw_patch) 276 | ctx->p += jg2_json_purify(ctx->p, (char *)ctx->body, m, 277 | &inlim_totlen); 278 | else { 279 | if (inlim_totlen > (size_t)m) 280 | inlim_totlen = m; 281 | memcpy(ctx->p, ctx->body, inlim_totlen); 282 | ctx->p += inlim_totlen; 283 | } 284 | ctx->body += inlim_totlen; 285 | 286 | if (!*ctx->body) 287 | ctx->body = NULL; 288 | 289 | if (!ctx->raw_patch && !ctx->body) { 290 | CTX_BUF_APPEND("\"\n"); 291 | 292 | if (ctx->lac) 293 | CTX_BUF_APPEND(",\n \"diff\": \""); 294 | else 295 | CTX_BUF_APPEND("}\n"); 296 | } 297 | 298 | if (ctx->raw_patch && !ctx->body) 299 | CTX_BUF_APPEND("\n\n"); 300 | } 301 | 302 | /* 303 | * We're going to walk the lac 304 | */ 305 | 306 | while (!ctx->body && ctx->lac && JG2_HAS_SPACE(ctx, 512)) { 307 | size_t inlim_totlen = 84; /* control chars may bloat to 6x */ 308 | char *p = ((char *)ctx->lac) + ctx->ofs; 309 | 310 | if (!ctx->size) { 311 | ctx->size = (unsigned char)*p++; 312 | ctx->size |= ((unsigned char)*p++) << 8; 313 | ctx->size |= ((unsigned char)*p++) << 16; 314 | ctx->size |= ((unsigned char)*p++) << 24; 315 | 316 | ctx->pos = 0; 317 | ctx->ofs += 4; 318 | } 319 | 320 | if (inlim_totlen > ctx->size - ctx->pos) 321 | inlim_totlen = ctx->size - ctx->pos; 322 | 323 | if (ctx->raw_patch) { 324 | memcpy(ctx->p, p, inlim_totlen); 325 | ctx->p += inlim_totlen; 326 | } else 327 | ctx->p += jg2_json_purify(ctx->p, p, 328 | lws_ptr_diff(ctx->end, ctx->p) - 4, 329 | &inlim_totlen); 330 | ctx->pos += inlim_totlen; 331 | ctx->ofs += inlim_totlen; 332 | 333 | if (!inlim_totlen) { 334 | lwsl_err("%s: hit NUL\n", __func__); 335 | goto ended; 336 | } 337 | 338 | if (ctx->pos == ctx->size) { 339 | ctx->size = 0; 340 | ctx->ofs = lwsac_align(ctx->ofs); 341 | ctx->pos = 0; 342 | } 343 | 344 | if (ctx->ofs >= lwsac_get_tail_pos(ctx->lac)) { 345 | ctx->lac = lwsac_get_next(ctx->lac); 346 | ctx->pos = 0; 347 | ctx->ofs = lwsac_sizeof(0); 348 | if (!ctx->lac) 349 | goto ended; 350 | } 351 | } 352 | if (!ctx->body && !ctx->lac) 353 | goto ended; 354 | 355 | goto done; 356 | 357 | ended: 358 | if (!ctx->raw_patch) 359 | meta_trailer(ctx, "\""); 360 | ctx->job = NULL; 361 | ctx->final = 1; 362 | ctx->body = NULL; 363 | job_commit_destroy(ctx); 364 | 365 | done: 366 | return 0; 367 | } 368 | -------------------------------------------------------------------------------- /lib/job/log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjg2 - log 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "../private.h" 23 | 24 | #include 25 | 26 | static int 27 | job_log_start(struct jg2_ctx *ctx) 28 | { 29 | git_generic_ptr u; 30 | git_oid oid; 31 | int error; 32 | 33 | error = jg2_oid_lookup(ctx->jrepo->repo, &oid, ctx->hex_oid); 34 | if (error) 35 | return error; 36 | 37 | error = git_object_lookup(&u.obj, ctx->jrepo->repo, &oid, GIT_OBJ_ANY); 38 | if (error < 0) 39 | return -1; 40 | 41 | if (git_object_type(u.obj) != GIT_OBJ_COMMIT) { 42 | git_object_free(u.obj); 43 | 44 | return -1; 45 | } 46 | 47 | ctx->u = u; 48 | meta_header(ctx); 49 | 50 | job_common_header(ctx); 51 | 52 | CTX_BUF_APPEND("\"log\": ["); 53 | ctx->subsequent = 0; 54 | 55 | return 0; 56 | } 57 | 58 | static void 59 | job_log_destroy(struct jg2_ctx *ctx) 60 | { 61 | if (ctx->u.commit) { 62 | git_commit_free(ctx->u.commit); 63 | ctx->u.commit = NULL; 64 | } 65 | ctx->job = NULL; 66 | } 67 | 68 | int 69 | job_log(struct jg2_ctx *ctx) 70 | { 71 | if (ctx->destroying) { 72 | job_log_destroy(ctx); 73 | 74 | return 0; 75 | } 76 | 77 | if (!ctx->partway && job_log_start(ctx)) { 78 | lwsl_err("%s: start failed (%s)\n", __func__, ctx->hex_oid); 79 | return -1; 80 | } 81 | 82 | while (JG2_HAS_SPACE(ctx, 768)) { 83 | git_commit *c; 84 | 85 | if (!ctx->u.obj || !ctx->count) { 86 | CTX_BUF_APPEND("\n]"); 87 | 88 | if (ctx->u.obj) { 89 | git_commit_parent(&c, ctx->u.commit, 0); 90 | if (c) { 91 | CTX_BUF_APPEND(", \"next\": "); 92 | 93 | jg2_json_oid(git_commit_id(c), ctx); 94 | 95 | git_commit_free(c); 96 | } 97 | } 98 | 99 | meta_trailer(ctx, ""); 100 | job_log_destroy(ctx); 101 | return 0; 102 | } 103 | 104 | CTX_BUF_APPEND("%c\n{ \"name\": ", 105 | ctx->subsequent ? ',' : ' '); 106 | 107 | ctx->subsequent = 1; 108 | 109 | jg2_json_oid(git_commit_id(ctx->u.commit), ctx); 110 | 111 | CTX_BUF_APPEND(",\n" 112 | "\"summary\": {\n"); 113 | 114 | commit_summary(ctx->u.commit, ctx); 115 | 116 | CTX_BUF_APPEND("}}"); 117 | 118 | c = NULL; 119 | git_commit_parent(&c, ctx->u.commit, 0); 120 | git_commit_free(ctx->u.commit); 121 | ctx->u.commit = c; 122 | 123 | if (ctx->count) 124 | ctx->count--; 125 | } 126 | 127 | return 0; 128 | } 129 | -------------------------------------------------------------------------------- /lib/job/no-snapshot.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjg2 - snapshot 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "../private.h" 23 | 24 | int 25 | job_snapshot(struct jg2_ctx *ctx) 26 | { 27 | return -1; 28 | } 29 | -------------------------------------------------------------------------------- /lib/job/plain.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjg2 - plain 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "../private.h" 23 | 24 | #include 25 | #include 26 | 27 | static int 28 | job_plain_start(struct jg2_ctx *ctx) 29 | { 30 | ctx->meta_last_job = 1; 31 | return blob_from_commit(ctx); 32 | } 33 | 34 | static void 35 | job_plain_destroy(struct jg2_ctx *ctx) 36 | { 37 | if (ctx->u.blob) { 38 | git_blob_free(ctx->u.blob); 39 | ctx->u.blob = NULL; 40 | } 41 | ctx->job = NULL; 42 | } 43 | 44 | 45 | int 46 | job_plain(struct jg2_ctx *ctx) 47 | { 48 | size_t m; 49 | 50 | if (ctx->destroying) { 51 | job_plain_destroy(ctx); 52 | 53 | return 0; 54 | } 55 | 56 | if (!ctx->partway && job_plain_start(ctx)) { 57 | lwsl_err("%s: start failed (%s)\n", __func__, ctx->hex_oid); 58 | 59 | return -1; 60 | } 61 | 62 | if (ctx->body && JG2_HAS_SPACE(ctx, 1)) { 63 | /* we're sending a blob */ 64 | 65 | m = lws_ptr_diff(ctx->end, ctx->p) - 1; 66 | 67 | if (m > ctx->size - ctx->pos) 68 | m = ctx->size - ctx->pos; 69 | 70 | memcpy(ctx->p, (char *)ctx->body + ctx->pos, m); 71 | ctx->pos += m; 72 | ctx->p += m; 73 | 74 | if (ctx->pos == ctx->size) { 75 | ctx->final = 1; 76 | job_plain_destroy(ctx); 77 | } 78 | } 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /lib/job/private.h: -------------------------------------------------------------------------------- 1 | typedef enum { 2 | JG2_JOB_REFLIST, 3 | JG2_JOB_LOG, 4 | JG2_JOB_COMMIT, 5 | JG2_JOB_PATCH, 6 | JG2_JOB_TREE, 7 | JG2_JOB_PLAIN, 8 | JG2_JOB_REPOLIST, 9 | JG2_JOB_SNAPSHOT, 10 | JG2_JOB_BLAME, 11 | JG2_JOB_BLOG, 12 | JG2_JOB_SEARCH, 13 | 14 | JG2_JOB_SEARCH_TRIE = 99 15 | } jg2_job_enum; 16 | 17 | typedef enum { 18 | EMIT_STATE_SUMMARY, 19 | EMIT_STATE_SUMMARY_LOG, 20 | 21 | EMIT_STATE_LOG, 22 | 23 | EMIT_STATE_COMMITBODY, 24 | 25 | EMIT_STATE_PATCH, 26 | 27 | EMIT_STATE_TAGS, 28 | 29 | EMIT_STATE_BRANCHES, 30 | 31 | EMIT_STATE_TREE, 32 | 33 | EMIT_STATE_PLAIN, 34 | 35 | EMIT_STATE_REPOLIST, 36 | 37 | EMIT_STATE_SNAPSHOT, 38 | 39 | EMIT_STATE_BLAME, 40 | 41 | EMIT_STATE_BLOG, 42 | 43 | EMIT_STATE_SEARCH, 44 | 45 | } jg2_job_state; 46 | 47 | enum rei_string_index { 48 | REI_STRING_NAME, 49 | REI_STRING_CONFIG_DESC, 50 | REI_STRING_CONFIG_OWNER, 51 | REI_STRING_CONFIG_URL 52 | }; 53 | 54 | /* 55 | * Indicates this job is a consequence of the immediately previous job. If 56 | * storing the results into the cache, it means this job should be appended to 57 | * previously used cache file and not treated as somthing on its own. 58 | */ 59 | 60 | #define JG2_JOB_FLAG_FINAL 1 61 | #define JG2_JOB_FLAG_CHAINED 128 62 | 63 | /** 64 | * jg2_job() - produce output from the job 65 | * 66 | * \param ctx: pointer to the context 67 | * 68 | * Writes a buffer with JSON from the execution of a "job". 69 | * 70 | * buf and len (length of buf) set where the JSON will be written. 71 | * 72 | * If the output is incomplete, 1 will be returned. If the written chunk 73 | * of JSON is the last in the "job", 0 will be returned. 74 | * 75 | * Job functions set the context job to NULL when they complete the last chunk. 76 | */ 77 | typedef int (*jg2_job)(struct jg2_ctx *ctx); 78 | 79 | 80 | /** 81 | * jg2_jobs() - Get pointer to jobs table 82 | * 83 | * \param n: One of JG2_job_enum (JG2_JOB_...) 84 | * 85 | * Returns a function pointer to the requested "job" the library can statefully 86 | * perform in a context. 87 | */ 88 | jg2_job 89 | jg2_get_job(jg2_job_enum n); 90 | 91 | /** 92 | * jg2_ctx_get_job() - get the current "job" the context is doing 93 | * 94 | * \param ctx: pointer to the context 95 | * 96 | * Returns NULL if no active job, or the pointer to the job function 97 | */ 98 | jg2_job 99 | jg2_ctx_get_job(struct jg2_ctx *ctx); 100 | 101 | 102 | /* 103 | * Is the mode going to produce JSON? Return 0. Otherwise return the 104 | * JG2_JOB index for the type of naked content it wants to produce. 105 | */ 106 | int 107 | jg2_job_naked(struct jg2_ctx *ctx); 108 | 109 | void 110 | meta_header(struct jg2_ctx *ctx); 111 | 112 | void 113 | job_common_header(struct jg2_ctx *ctx); 114 | 115 | void 116 | meta_trailer(struct jg2_ctx *ctx, const char *term); 117 | 118 | void 119 | __jg2_job_compute_cache_hash(struct jg2_ctx *ctx, jg2_job_enum job, int count, 120 | char *md5_hex33); 121 | 122 | const char * 123 | jg2_rei_string(const struct repo_entry_info *rei, enum rei_string_index n); 124 | 125 | int 126 | job_search_check_indexed(struct jg2_ctx *ctx, uint32_t *files, uint32_t *done); 127 | 128 | /* jobs */ 129 | 130 | int 131 | job_reflist(struct jg2_ctx *ctx); 132 | 133 | int 134 | job_log(struct jg2_ctx *ctx); 135 | 136 | int 137 | job_commit(struct jg2_ctx *ctx); 138 | 139 | int 140 | job_tree(struct jg2_ctx *ctx); 141 | 142 | int 143 | job_plain(struct jg2_ctx *ctx); 144 | 145 | int 146 | job_repos(struct jg2_ctx *ctx); 147 | 148 | int 149 | job_snapshot(struct jg2_ctx *ctx); 150 | 151 | int 152 | job_blame(struct jg2_ctx *ctx); 153 | 154 | int 155 | job_blog(struct jg2_ctx *ctx); 156 | 157 | int 158 | job_search(struct jg2_ctx *ctx); 159 | -------------------------------------------------------------------------------- /lib/job/reflist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjg2 - ref list 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "../private.h" 23 | 24 | static int 25 | job_reflist_start(struct jg2_ctx *ctx) 26 | { 27 | if (git_reference_iterator_new(&ctx->iter_ref, ctx->jrepo->repo) < 0) 28 | return -1; 29 | 30 | meta_header(ctx); 31 | 32 | job_common_header(ctx); 33 | CTX_BUF_APPEND("\"reflist\": ["); 34 | 35 | return 0; 36 | } 37 | 38 | static void 39 | job_reflist_destroy(struct jg2_ctx *ctx) 40 | { 41 | if (ctx->iter_ref) { 42 | git_reference_iterator_free(ctx->iter_ref); 43 | ctx->iter_ref = NULL; 44 | } 45 | ctx->job = NULL; 46 | } 47 | 48 | int 49 | job_reflist(struct jg2_ctx *ctx) 50 | { 51 | if (ctx->destroying) { 52 | job_reflist_destroy(ctx); 53 | 54 | return 0; 55 | } 56 | 57 | if (!ctx->partway && job_reflist_start(ctx)) 58 | return -1; 59 | 60 | while (JG2_HAS_SPACE(ctx, 768)) { 61 | char pure[128]; 62 | git_reference *gref, *rref; 63 | const git_oid *oid; 64 | 65 | /* 66 | * This will list 'commit' and 'tag' objects... 67 | * 'commit' objects named refs/heads/xxx represent branch xxx 68 | */ 69 | 70 | if (git_reference_next(&gref, ctx->iter_ref) < 0) { 71 | meta_trailer(ctx, "\n]"); 72 | job_reflist_destroy(ctx); 73 | break; 74 | } 75 | 76 | git_reference_resolve(&rref, gref); 77 | oid = git_reference_target(rref); 78 | if (oid) { 79 | jg2_json_purify(pure, git_reference_name(gref), 80 | sizeof(pure), NULL); 81 | CTX_BUF_APPEND("%c\n{ \"name\": \"%s\"," 82 | "\"summary\": ", 83 | ctx->subsequent ? ',' : ' ', pure); 84 | ctx->subsequent = 1; 85 | generic_object_summary(oid, ctx); 86 | 87 | CTX_BUF_APPEND("}"); 88 | } 89 | 90 | git_reference_free(rref); 91 | git_reference_free(gref); 92 | } 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /lib/job/repos.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjsongit2 - commit 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "../private.h" 23 | 24 | #include 25 | #include 26 | 27 | #define lp_to_rei(p, _n) lws_list_ptr_container(p, struct repo_entry_info, _n) 28 | 29 | static int 30 | job_repos_start(struct jg2_ctx *ctx) 31 | { 32 | ctx->rei = lp_to_rei(ctx->vhost->repodir->rei_head, next); 33 | 34 | meta_header(ctx); 35 | 36 | job_common_header(ctx); 37 | CTX_BUF_APPEND("\"repolist\":["); 38 | 39 | ctx->subsequent = 0; 40 | 41 | return 0; 42 | } 43 | 44 | static void 45 | job_repos_destroy(struct jg2_ctx *ctx) 46 | { 47 | ctx->job = NULL; 48 | } 49 | 50 | int 51 | job_repos(struct jg2_ctx *ctx) 52 | { 53 | char name[256]; 54 | char *p; 55 | 56 | if (ctx->destroying) { 57 | job_repos_destroy(ctx); 58 | 59 | return 0; 60 | } 61 | 62 | if (!ctx->partway && job_repos_start(ctx)) { 63 | lwsl_err("%s: start failed (%s)\n", __func__, ctx->hex_oid); 64 | return -1; 65 | } 66 | 67 | if (!ctx->rei) 68 | goto empty; 69 | 70 | while (ctx->rei) { 71 | int size = ctx->rei->name_len + ctx->rei->conf_len[0] + 72 | ctx->rei->conf_len[1] + ctx->rei->conf_len[2]; 73 | 74 | if (!JG2_HAS_SPACE(ctx, 100 + size)) 75 | break; 76 | 77 | p = (char *)(ctx->rei + 1); 78 | 79 | 80 | pthread_mutex_lock(&ctx->vhost->lock); /* ======== vhost lock */ 81 | 82 | if (jg2_acl_check(ctx, p, ctx->acl_user)) { 83 | pthread_mutex_unlock(&ctx->vhost->lock); /*vhost lock */ 84 | goto next; 85 | } 86 | 87 | pthread_mutex_unlock(&ctx->vhost->lock); /* ------ vhost lock */ 88 | 89 | CTX_BUF_APPEND("%c\n{ \"reponame\": \"%s\"", 90 | ctx->subsequent ? ',' : ' ', 91 | ellipsis_purify(name, (char *)p, sizeof(name))); 92 | ctx->subsequent = 1; 93 | p += ctx->rei->name_len; 94 | 95 | if (ctx->rei->conf_len[0]) { 96 | CTX_BUF_APPEND(",\"desc\": \"%s\"", 97 | ellipsis_purify(name, (char *)p, sizeof(name))); 98 | 99 | p += ctx->rei->conf_len[0]; 100 | } 101 | if (ctx->rei->conf_len[1]) { 102 | if (ctx->rei->conf_len[0]) 103 | CTX_BUF_APPEND(", "); 104 | 105 | identity_json(p, ctx); 106 | p += ctx->rei->conf_len[1]; 107 | } 108 | 109 | if (ctx->rei->conf_len[2]) { 110 | if (ctx->rei->conf_len[0] || ctx->rei->conf_len[1]) 111 | CTX_BUF_APPEND(", "); 112 | 113 | CTX_BUF_APPEND(" \"url\": \"%s\"", 114 | ellipsis_purify(name, (char *)p, sizeof(name))); 115 | } 116 | 117 | CTX_BUF_APPEND("}"); 118 | 119 | next: 120 | ctx->rei = lp_to_rei(ctx->rei->next, next); 121 | if (ctx->rei) 122 | continue; 123 | empty: 124 | meta_trailer(ctx, "]"); 125 | job_repos_destroy(ctx); 126 | } 127 | 128 | return 0; 129 | } 130 | -------------------------------------------------------------------------------- /lib/job/tree.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjsongit2 - tree 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "../private.h" 23 | 24 | #include 25 | #include 26 | 27 | #define lp_to_te(p, _n) lws_list_ptr_container(p, struct tree_entry_info, _n) 28 | 29 | static int 30 | tei_alpha_sort(lws_list_ptr a, lws_list_ptr b) 31 | { 32 | struct tree_entry_info *p1 = lp_to_te(a, next), 33 | *p2 = lp_to_te(b, next); 34 | 35 | /* directories go at the top */ 36 | if ((p1->mode & 16384) != (p2->mode & 16384)) 37 | return !!(p2->mode & 16384) - !!(p1->mode & 16384); 38 | 39 | return strcmp((const char *)(p1 + 1), (const char *)(p2 + 1)); 40 | } 41 | 42 | /* 43 | * For efficiency, we dump results linearly in a linked-list of "chunk" 44 | * allocations, adding to it as needed. It means we have much less allocation 45 | * that using one per-object. See ./lib/lac/README.md 46 | * 47 | * Each entry is part of a sorted linked-list held inside the 48 | * struct tree_entry_info 49 | * 50 | * The objects consist of 51 | * 52 | * [ struct tree_entry_info ] [ name string NUL terminated ] 53 | * 54 | */ 55 | 56 | static int 57 | treewalk_cb(const char *root, const git_tree_entry *entry, void *payload) 58 | { 59 | struct jg2_ctx *ctx = payload; 60 | struct tree_entry_info *tei; 61 | const char *name; 62 | git_blob *blob; 63 | git_otype type; 64 | size_t m; 65 | 66 | type = git_tree_entry_type(entry); 67 | name = git_tree_entry_name(entry); 68 | m = strlen(name) + 1; 69 | 70 | tei = lwsac_use(&ctx->lwsac_head, sizeof(*tei) + m, 0); 71 | if (!tei) { 72 | lwsl_err("OOM\n"); 73 | 74 | return -1; 75 | } 76 | 77 | tei->oid = git_tree_entry_id(entry); 78 | tei->mode = git_tree_entry_filemode(entry); 79 | tei->namelen = m - 1; 80 | tei->type = type; 81 | tei->size = 0; 82 | 83 | if (type == GIT_OBJ_BLOB && 84 | !git_blob_lookup(&blob, ctx->jrepo->repo, tei->oid)) { 85 | tei->size = git_blob_rawsize(blob); 86 | git_blob_free(blob); 87 | } 88 | 89 | /* copy the name into place; lac is already advanced and aligned */ 90 | 91 | memcpy(tei + 1, name, m); 92 | 93 | lws_list_ptr_insert(&ctx->sorted_head, &tei->next, tei_alpha_sort); 94 | 95 | return type == GIT_OBJ_TREE; /* don't go inside trees */ 96 | } 97 | 98 | static void 99 | job_tree_destroy(struct jg2_ctx *ctx) 100 | { 101 | lwsac_free(&ctx->lwsac_head); 102 | ctx->sorted_head = NULL; 103 | 104 | if (ctx->u.tree) { 105 | lwsl_debug("%s: free tree %p\n", __func__, ctx->u.tree); 106 | git_tree_free(ctx->u.tree); 107 | ctx->u.tree = NULL; 108 | } 109 | ctx->job = NULL; 110 | } 111 | 112 | static int 113 | job_tree_start(struct jg2_ctx *ctx) 114 | { 115 | git_tree_entry *te; 116 | git_generic_ptr u; 117 | const char *epath = ctx->sr.e[JG2_PE_PATH]; 118 | char pure[256], entry_did_inline = ctx->did_inline; 119 | git_commit *c; 120 | git_oid oid; 121 | int e; 122 | 123 | if (!ctx->hex_oid[0]) { 124 | lwsl_err("%s: no oid\n", __func__); 125 | 126 | return 1; 127 | } 128 | 129 | if (!ctx->jrepo) { 130 | lwsl_err("%s: no jrepo\n", __func__); 131 | 132 | return 1; 133 | } 134 | 135 | ctx->count = 0; 136 | ctx->pos = 0; 137 | ctx->tei = NULL; 138 | 139 | e = jg2_oid_lookup(ctx->jrepo->repo, &oid, ctx->hex_oid); 140 | if (e) 141 | return e; 142 | 143 | 144 | e = git_object_lookup(&u.obj, ctx->jrepo->repo, &oid, GIT_OBJ_ANY); 145 | if (e < 0) { 146 | lwsl_err("git_object_lookup failed\n"); 147 | return -1; 148 | } 149 | 150 | if (git_object_type(u.obj) != GIT_OBJ_COMMIT) { 151 | lwsl_err("git object not a commit\n"); 152 | goto bail; 153 | } 154 | 155 | /* convert the commit object to a tree object */ 156 | 157 | c = u.commit; 158 | if (git_commit_tree(&u.tree, u.commit)) { 159 | lwsl_err("no tree from commit\n"); 160 | goto bail; 161 | } 162 | 163 | // lwsl_err("%s: touched tree %p +++++\n", __func__, u.tree); 164 | 165 | git_commit_free(c); 166 | 167 | // lwsl_notice("%s\n", __func__); 168 | 169 | if (!ctx->did_inline && ctx->inline_filename[0]) { 170 | epath = ctx->inline_filename; 171 | lwsl_notice("using inline_filename %s\n", ctx->inline_filename); 172 | ctx->did_inline = 1; 173 | } 174 | 175 | if (epath && epath[0]) { 176 | if (git_tree_entry_bypath(&te, u.tree, epath)) { 177 | lwsl_info("%s: git_tree_entry_bypath %s failed\n", 178 | __func__, epath); 179 | lws_snprintf(ctx->status, sizeof(ctx->status), 180 | "Path '%s' doesn't exist in revision '%s'", 181 | epath, ctx->hex_oid); 182 | 183 | goto bail; 184 | } 185 | 186 | // lwsl_err("%s: free tree 1 %p\n", __func__, u.tree); 187 | git_tree_free(u.tree); 188 | u.tree = NULL; 189 | 190 | e = git_tree_entry_to_object(&u.obj, ctx->jrepo->repo, te); 191 | git_tree_entry_free(te); 192 | if (e) { 193 | lwsl_err("git_tree_entry_to_object failed\n"); 194 | goto bail; 195 | } 196 | 197 | // lwsl_err("%s: touch tree 1 %p\n", __func__, u.tree); 198 | } 199 | 200 | ctx->u = u; 201 | 202 | /* 203 | * /tree/ mode urls are followed by a "path" element inside the tree. 204 | * 205 | * These can consist of either a dir path, which we want to use to 206 | * restrict where we walk the tree, or a blob path. 207 | * 208 | * If it's a blob path, we don't walk the tree to show the dir listing, 209 | * but show the blob as best we can. 210 | */ 211 | 212 | if (git_object_type(u.obj) == GIT_OBJ_BLOB) { 213 | const char *p = epath, *p1; 214 | 215 | ctx->body = git_blob_rawcontent(u.blob); 216 | ctx->size = git_blob_rawsize(u.blob); 217 | ctx->pos = 0; 218 | ctx->appended_blob = 1; 219 | 220 | // lwsl_notice("%s: blob\n", __func__); 221 | 222 | ctx->meta_last_job = 1; 223 | 224 | /* do some searchubf */ 225 | 226 | if (ctx->sr.e[JG2_PE_SEARCH] && !ctx->did_sat && 227 | ctx->sr.e[JG2_PE_PATH]) { 228 | ctx->meta_last_job = 0; 229 | ctx->blame_after_tree = 230 | !strcmp(ctx->sr.e[JG2_PE_MODE], "blame"); 231 | } 232 | 233 | /* countermand the FINAL if actually more to do ... */ 234 | 235 | if (ctx->sr.e[JG2_PE_MODE] && !entry_did_inline && 236 | !strcmp(ctx->sr.e[JG2_PE_MODE], "blame")) { 237 | /* we want to send blame info after this */ 238 | ctx->meta_last_job = 0; 239 | ctx->blame_after_tree = 240 | !strcmp(ctx->sr.e[JG2_PE_MODE], "blame"); 241 | } 242 | 243 | meta_header(ctx); 244 | pure[0] = '\0'; 245 | 246 | if (p) { 247 | p1 = strrchr(p + strlen(p) - 1, '/'); 248 | if (p1) 249 | p = p1 + 1; 250 | } 251 | ellipsis_purify(pure, p, sizeof(pure)); 252 | 253 | job_common_header(ctx); 254 | 255 | CTX_BUF_APPEND("\"oid\":"); 256 | jg2_json_oid(&oid, ctx); 257 | CTX_BUF_APPEND(",\"blobname\": \"%s\", ", pure); 258 | 259 | if (!git_blob_is_binary(u.blob)) { 260 | CTX_BUF_APPEND("\"blob\": \""); 261 | return 0; 262 | } 263 | 264 | strncpy(pure, ctx->vhost->cfg.virtual_base_urlpath, 265 | sizeof(pure) - 1); 266 | pure[sizeof(pure) - 1] = '\0'; 267 | 268 | if (strlen(pure) > 1) 269 | strcat(pure, "/"); 270 | 271 | ellipsis_purify(pure + strlen(pure), ctx->sr.e[JG2_PE_NAME], 272 | sizeof(pure) - strlen(pure)); 273 | CTX_BUF_APPEND(" \"bloblink\": \"%s/plain/", pure); 274 | ellipsis_purify(pure, epath, sizeof(pure) - 5); 275 | 276 | strcat(pure, "\" "); 277 | 278 | git_blob_free(u.blob); 279 | ctx->u.obj = NULL; 280 | ctx->body = NULL; 281 | 282 | meta_trailer(ctx, pure); 283 | job_tree_destroy(ctx); 284 | 285 | return 0; 286 | } 287 | 288 | /* so the first part is walk the tree level and collect the objects */ 289 | 290 | e = git_tree_walk(ctx->u.tree, GIT_TREEWALK_PRE, treewalk_cb, ctx); 291 | if (e < 0) { 292 | const git_error *er = giterr_last(); 293 | 294 | lwsl_err("Failed to collect the tree objects: %d\n", e); 295 | if (er) 296 | lwsl_err("err %d: %s\n", er->klass, er->message); 297 | 298 | goto bail; 299 | } 300 | 301 | ctx->tei = lp_to_te(ctx->sorted_head, next); 302 | 303 | meta_header(ctx); 304 | 305 | CTX_BUF_APPEND("{ \"schema\":\"libjg2-1\",\n \"oid\":"); 306 | jg2_json_oid(&oid, ctx); 307 | CTX_BUF_APPEND(",\"tree\": ["); 308 | 309 | // lwsl_notice("%s: exit OK\n", __func__); 310 | 311 | return 0; 312 | 313 | bail: 314 | if (u.obj) { 315 | git_object_free(u.obj); 316 | u.obj = NULL; 317 | } 318 | 319 | lwsac_free(&ctx->lwsac_head); 320 | 321 | ctx->failed_in_start = 1; 322 | job_tree_destroy(ctx); 323 | 324 | return -1; 325 | } 326 | 327 | struct inline_match { 328 | const char *name; 329 | unsigned char len; 330 | }; 331 | 332 | /* in order of best preference... an entry earlier in this table is more 333 | * preferable and can replace an already-matched entry from later in the 334 | * table */ 335 | 336 | static struct inline_match inline_match[] = { 337 | { "README.md", 9 }, 338 | { "README", 6 }, 339 | { ".mkd", 4 }, 340 | { ".md", 3 }, 341 | }; 342 | 343 | int 344 | job_tree(struct jg2_ctx *ctx) 345 | { 346 | struct tree_entry_info *head; 347 | char name[128]; 348 | size_t m; 349 | 350 | if (ctx->destroying) { 351 | job_tree_destroy(ctx); 352 | 353 | return 0; 354 | } 355 | 356 | // lwsl_err("%s: entry\n", __func__); 357 | 358 | if (!ctx->partway && job_tree_start(ctx)) { 359 | lwsl_err("%s: start failed (%s)\n", __func__, ctx->hex_oid); 360 | return -1; 361 | } 362 | 363 | if (ctx->body) { 364 | /* we're sending a blob */ 365 | 366 | size_t inlim_totlen = ctx->size - ctx->pos; 367 | 368 | // lwsl_err("%s: sending blob\n", __func__); 369 | 370 | m = lws_ptr_diff(ctx->end, ctx->p) - 1; 371 | if (m < JG2_RESERVE_SEAL + 24) 372 | return 0; 373 | 374 | m -= JG2_RESERVE_SEAL; 375 | 376 | if (inlim_totlen > m / 6) 377 | inlim_totlen = m / 6; 378 | 379 | if (!inlim_totlen) 380 | inlim_totlen = 1; 381 | 382 | ctx->p += jg2_json_purify(ctx->p, (char *)ctx->body + ctx->pos, 383 | m, &inlim_totlen); 384 | ctx->pos += inlim_totlen; 385 | 386 | // lwsl_notice("%s: %.*s\n", __func__, (int)m, (char *)ctx->body + ctx->pos); 387 | 388 | if (ctx->pos == ctx->size) { 389 | // lwsl_err("%s: blob reached size\n", __func__); 390 | 391 | 392 | meta_trailer(ctx, "\""); 393 | job_tree_destroy(ctx); 394 | } 395 | } 396 | 397 | head = lp_to_te(ctx->sorted_head, next); 398 | 399 | while (ctx->tei) { 400 | const char *tei_name = (const char *)(ctx->tei + 1); 401 | size_t n; 402 | 403 | if (!JG2_HAS_SPACE(ctx, 250 + ctx->tei->namelen)) { 404 | // lwsl_err("%s: JG2_HAS_SPACE failed\n", __func__); 405 | 406 | break; 407 | } 408 | 409 | CTX_BUF_APPEND("%c\n{ \"name\": \"%s\"," 410 | "\"mode\": \"%u\", \"size\":%llu}", 411 | ctx->tei != head ? ',' : ' ', 412 | ellipsis_purify(name, tei_name, sizeof(name)), 413 | (unsigned int)ctx->tei->mode, 414 | (unsigned long long)ctx->tei->size); 415 | 416 | /* is this file in the file listing an inline doc file? */ 417 | 418 | for (n = 0; n < LWS_ARRAY_SIZE(inline_match); n++) { 419 | /* 420 | * name has to be long enough to match; the end of the 421 | * name must match the inline_match name; and we must 422 | * not already have matched on the same or more 423 | * preferable inline_match entry 424 | */ 425 | if (ctx->tei->namelen < inline_match[n].len || 426 | strcmp(tei_name + ctx->tei->namelen - 427 | inline_match[n].len, inline_match[n].name) || 428 | (ctx->if_pref && ctx->if_pref <= n + 1)) 429 | continue; 430 | 431 | if (ctx->sr.e[JG2_PE_PATH] && strlen(ctx->sr.e[JG2_PE_PATH])) 432 | if (ctx->sr.e[JG2_PE_PATH][strlen(ctx->sr.e[JG2_PE_PATH]) - 1] == '/') 433 | lws_snprintf(ctx->inline_filename, 434 | sizeof(ctx->inline_filename), 435 | "%s%s", ctx->sr.e[JG2_PE_PATH], 436 | tei_name); 437 | else 438 | lws_snprintf(ctx->inline_filename, 439 | sizeof(ctx->inline_filename), 440 | "%s/%s", ctx->sr.e[JG2_PE_PATH], 441 | tei_name); 442 | else 443 | lws_snprintf(ctx->inline_filename, 444 | sizeof(ctx->inline_filename), 445 | "%s", tei_name); 446 | 447 | /* 448 | * note how preferable this match is, so we can 449 | * supercede it if we find a better match 450 | */ 451 | ctx->if_pref = n + 1; 452 | 453 | /* we have a new job after this now... */ 454 | ctx->meta_last_job = 0; 455 | break; 456 | } 457 | 458 | ctx->tei = lp_to_te(ctx->tei->next, next); 459 | 460 | if (ctx->tei) 461 | continue; 462 | 463 | meta_trailer(ctx, "]"); 464 | job_tree_destroy(ctx); 465 | } 466 | 467 | return 0; 468 | } 469 | -------------------------------------------------------------------------------- /lib/repostate.c: -------------------------------------------------------------------------------- 1 | /* 2 | * libjsongit2 - repostate.c 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include "private.h" 23 | 24 | #include 25 | 26 | /* vhost lock must be held on entry */ 27 | 28 | int 29 | __repo_reflist_update(struct jg2_vhost *vh, struct jg2_repo *jrepo) 30 | { 31 | struct jg2_ref **er_prev = &jrepo->ref_list, *er = *er_prev, *er1, 32 | **er_hash_prev[REF_HASH_SIZE]; 33 | unsigned char entry[REF_HASH_SIZE]; 34 | git_reference_iterator *iter_ref; 35 | int change_seen = 0, n, ret = -1; 36 | time_t t = time(NULL); 37 | struct jg2_ctx *ctx; 38 | git_reference *ref; 39 | 40 | /* limit how often we are willing to do this */ 41 | 42 | if (t - jrepo->last_update <= 3) 43 | return 0; 44 | 45 | pthread_mutex_lock(&jrepo->lock); /* ===================== jrepo lock */ 46 | 47 | memcpy(entry, jrepo->md5_refs, sizeof(entry)); 48 | 49 | jrepo->last_update = t; 50 | 51 | for (n = 0; n < REF_HASH_SIZE; n++) 52 | er_hash_prev[n] = &jrepo->ref_list_hash[n]; 53 | 54 | /* 55 | * First we iterate all the interesting refs and check against our 56 | * existing reflist. Most commonly, they are unchanged and we exit 57 | * with 0 indicating no change. 58 | * 59 | * We keep track of if the only difference was an oid change on an 60 | * existing ref. If so we update it on to go, recompute the repo hash 61 | * and we exit with 1, indicating change found. 62 | * 63 | * If the iterated refs and reflist lose coherence on the names, we 64 | * delete and reacquire the whole reflist, recompute the repo hash, and 65 | * exit with 2 indicating a change found. 66 | * 67 | * Because we may mark, eg, commit logs with decorations from any 68 | * other ref, if any ref changes all views are potentially affected. 69 | */ 70 | 71 | if (git_reference_iterator_new(&iter_ref, jrepo->repo) < 0) { 72 | if (giterr_last()) 73 | giterr_clear(); 74 | goto bail; 75 | } 76 | 77 | if (giterr_last()) 78 | giterr_clear(); 79 | 80 | while (git_reference_next(&ref, iter_ref) >= 0) { 81 | const char *name = git_reference_name(ref); 82 | 83 | if (!strncmp(name, "refs/heads/", 11) || 84 | !strncmp(name, "refs/tags/", 10)) { 85 | const git_oid *oid; 86 | 87 | if (!er) { /* lost coherence: extra new refs */ 88 | change_seen |= 2; 89 | break; 90 | } 91 | 92 | if (strcmp(name, er->ref_name)) { 93 | /* lost coherence: wrong name */ 94 | change_seen |= 2; 95 | break; 96 | } 97 | 98 | oid = git_reference_target(ref); 99 | 100 | if (git_oid_cmp(&er->oid, oid)) { 101 | change_seen |= 1; /* existing ref changed oid */ 102 | /* snip us out of existing hash table */ 103 | *er_hash_prev[jg2_oidbin(&er->oid)] = 104 | er->hash_next; 105 | /* update our oid */ 106 | git_oid_cpy(&er->oid, oid); 107 | /* patch us into hash table of the new oid */ 108 | er->hash_next = *er_hash_prev[jg2_oidbin(oid)]; 109 | *er_hash_prev[jg2_oidbin(oid)] = er; 110 | } 111 | er_hash_prev[jg2_oidbin(oid)] = &er->hash_next; 112 | er_prev = &er->next; 113 | er = er->next; 114 | } 115 | git_reference_free(ref); 116 | ref = NULL; 117 | } 118 | 119 | if (er) /* lost coherence: less refs than before */ 120 | change_seen |= 2; 121 | 122 | if (!change_seen) { 123 | git_reference_iterator_free(iter_ref); 124 | 125 | ret = 0; 126 | goto bail; 127 | } 128 | 129 | if (!(change_seen & 2)) 130 | /* ref has always been freed if !(change_seen & 2) */ 131 | goto changed1; 132 | 133 | /* snip all the existing refs past the point we lost coherence */ 134 | 135 | *er_prev = NULL; 136 | while (er) { 137 | /* snip us out of existing hash table */ 138 | *er_hash_prev[jg2_oidbin(&er->oid)] = er->hash_next; 139 | er1 = er->next; 140 | jg2_repo_ref_destroy(er); 141 | er = er1; 142 | } 143 | 144 | if (!ref) 145 | /* 146 | * we actually handled all the refs. The only problem was 147 | * some left over in the original list... 148 | */ 149 | goto changed1; 150 | 151 | /* regenerate the ref list from the point it lost coherence */ 152 | 153 | do { 154 | const char *name = git_reference_name(ref); 155 | 156 | if (!strncmp(name, "refs/heads/", 11) || 157 | !strncmp(name, "refs/tags/", 10)) { 158 | const git_oid *oid; 159 | 160 | oid = git_reference_target(ref); 161 | 162 | er = jg2_zalloc(sizeof(*er)); 163 | if (!er) 164 | goto bail; 165 | 166 | er->ref_name = strdup(name); 167 | git_oid_cpy(&er->oid, oid); 168 | 169 | *er_prev = er; 170 | er->next = NULL; 171 | *er_hash_prev[jg2_oidbin(&er->oid)] = er; 172 | er_hash_prev[jg2_oidbin(&er->oid)] = &er->hash_next; 173 | er_prev = &er->next; 174 | } 175 | 176 | git_reference_free(ref); 177 | } while (git_reference_next(&ref, iter_ref) >= 0); 178 | 179 | changed1: 180 | git_reference_iterator_free(iter_ref); 181 | 182 | /* create a new hash representing all the refs in this repo */ 183 | 184 | vh->cfg.md5_init(vh->md5_ctx); 185 | 186 | er = jrepo->ref_list; 187 | while (er) { 188 | // lwsl_debug("%s: %s\n", __func__, er->ref_name); 189 | 190 | vh->cfg.md5_upd(vh->md5_ctx, (unsigned char *)er->ref_name, 191 | strlen(er->ref_name)); 192 | vh->cfg.md5_upd(vh->md5_ctx, er->oid.id, sizeof(er->oid.id)); 193 | 194 | er = er->next; 195 | } 196 | 197 | vh->cfg.md5_fini(vh->md5_ctx, jrepo->md5_refs); 198 | 199 | { 200 | char hash33[33]; 201 | 202 | md5_to_hex_cstr(hash33, jrepo->md5_refs); 203 | 204 | if (memcmp(entry, jrepo->md5_refs, sizeof(entry))) 205 | lwsl_notice("%s: %s: ref hash: %s\n", __func__, 206 | jrepo->repo_path, hash33); 207 | } 208 | 209 | /* 210 | * Inform all ctx that use this repo about the refchange... this is 211 | * useful if the client is on a long poll... 212 | */ 213 | 214 | ctx = jrepo->ctx_repo_list; 215 | while (ctx) { 216 | if (ctx->vhost->cfg.refchange) 217 | ctx->vhost->cfg.refchange(ctx->user); 218 | 219 | ctx = ctx->ctx_using_repo_next; 220 | } 221 | 222 | ret = 2; 223 | 224 | bail: 225 | pthread_mutex_unlock(&jrepo->lock); /*------------------ jrepo unlock */ 226 | 227 | return ret; 228 | } 229 | 230 | int 231 | jg2_vhost_repo_reflist_update(struct jg2_vhost *vhost) 232 | { 233 | struct jg2_repo *r; 234 | int m = 0; 235 | 236 | pthread_mutex_lock(&vhost->lock); /* ===================== vhost lock */ 237 | 238 | r = vhost->repo_list; 239 | 240 | while (r) { 241 | m |= __repo_reflist_update(vhost, r); 242 | 243 | r = r->next; 244 | } 245 | 246 | pthread_mutex_unlock(&vhost->lock); /*------------------ vhost unlock */ 247 | 248 | return m; 249 | } 250 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * gitohashi - main.c 3 | * 4 | * Copyright (C) 2018 Andy Green 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation: 9 | * version 2.1 of the License. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | #include 23 | #include 24 | 25 | #define LWS_PLUGIN_STATIC 26 | #include "protocol_gitohashi.c" 27 | #include "protocol_avatar-proxy.c" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | 36 | #define LWSWS_CONFIG_STRING_SIZE (32 * 1024) 37 | 38 | static int interrupted; 39 | 40 | static const struct lws_protocols protocols[] = { 41 | LWS_PLUGIN_PROTOCOL_GITOHASHI, 42 | LWS_PLUGIN_PROTOCOL_AVATAR_PROXY, 43 | { } 44 | }; 45 | 46 | static struct lws_context * 47 | context_creation(const char *config_dir) 48 | { 49 | int cs_len = LWSWS_CONFIG_STRING_SIZE - 1; 50 | struct lws_context_creation_info info; 51 | struct lws_context *context; 52 | char *cs, *config_strings; 53 | 54 | cs = config_strings = malloc(LWSWS_CONFIG_STRING_SIZE); 55 | if (!config_strings) { 56 | lwsl_err("Unable to allocate config strings heap\n"); 57 | 58 | return NULL; 59 | } 60 | 61 | memset(&info, 0, sizeof(info)); 62 | 63 | info.external_baggage_free_on_destroy = config_strings; 64 | info.pt_serv_buf_size = 8192; 65 | info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | 66 | LWS_SERVER_OPTION_EXPLICIT_VHOSTS | 67 | LWS_SERVER_OPTION_VALIDATE_UTF8; 68 | 69 | lwsl_notice("Using config dir: \"%s\"\n", config_dir); 70 | 71 | /* 72 | * first go through the config for creating the outer context 73 | */ 74 | if (lwsws_get_config_globals(&info, config_dir, &cs, &cs_len)) 75 | goto init_failed; 76 | 77 | info.pcontext = &context; 78 | info.vhost_name = NULL; 79 | 80 | context = lws_create_context(&info); 81 | if (context == NULL) { 82 | lwsl_err("lws init failed\n"); 83 | /* config_strings freed as 'external baggage' */ 84 | return NULL; 85 | } 86 | 87 | info.protocols = protocols; 88 | 89 | if (lwsws_get_config_vhosts(context, &info, config_dir, &cs, &cs_len)) { 90 | lws_context_destroy(context); 91 | 92 | return NULL; 93 | } 94 | 95 | return context; 96 | 97 | init_failed: 98 | free(config_strings); 99 | 100 | return NULL; 101 | } 102 | 103 | void sigint_handler(int sig) 104 | { 105 | lwsl_err("signal %d\n", sig); 106 | interrupted = 1; 107 | } 108 | 109 | int main(int argc, const char **argv) 110 | { 111 | int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE 112 | /* | LLL_THREAD */; 113 | struct lws_context *context; 114 | const char *p; 115 | 116 | signal(SIGINT, sigint_handler); 117 | 118 | if ((p = lws_cmdline_option(argc, argv, "-d"))) 119 | logs = atoi(p); 120 | 121 | lws_set_log_level(logs, lwsl_emit_stderr_notimestamp); 122 | 123 | lwsl_user("Gitohashi - " 124 | "Copyright (C) 2018 Andy Green \n"); 125 | 126 | // git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0); 127 | 128 | context = context_creation("/etc/gitohashi"); 129 | if (!context) { 130 | lwsl_err("lws init failed\n"); 131 | return 1; 132 | } 133 | 134 | while (n >= 0 && !interrupted) { 135 | n = lws_service(context, 1000); 136 | if (n < 0) 137 | lwsl_notice("lws_service returned %d\n", n); 138 | } 139 | 140 | lws_context_destroy(context); 141 | context = NULL; 142 | 143 | return 0; 144 | } 145 | -------------------------------------------------------------------------------- /system/gitohashi-selinux.pp: -------------------------------------------------------------------------------- 1 | module gitohashi-selinux 1.0; 2 | 3 | require { 4 | type reserved_port_t; 5 | type unconfined_t; 6 | type httpd_t; 7 | class tcp_socket name_bind; 8 | class unix_stream_socket connectto; 9 | } 10 | 11 | #============= httpd_t ============== 12 | allow httpd_t reserved_port_t:tcp_socket name_bind; 13 | allow httpd_t unconfined_t:unix_stream_socket connectto; 14 | 15 | -------------------------------------------------------------------------------- /system/gitohashi.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Gitohashi 3 | After=syslog.target 4 | 5 | [Service] 6 | ExecStart=/usr/local/bin/gitohashi 7 | ExecReload=/usr/bin/kill -HUP $MAINPID 8 | ExecStop=/usr/bin/killall gitohashi 9 | Restart=on-failure 10 | RestartSec=5s 11 | LimitAS=1500M 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | 16 | -------------------------------------------------------------------------------- /templates/gitohashi-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | Gitohashi example html 19 | 20 | 21 | 22 |
23 | 24 | 25 | 28 | 29 | 30 | 49 | 50 |
26 | 27 | 31 | 34 | 35 | 36 | 38 | 40 | 42 | 44 | 46 | 47 | 48 |
32 | Gitohashi is a modern, flexible and fast C gitweb-type interface 33 |
37 | Project homepage 39 | Mailing List 41 | Warmcat.com 43 | API Docs 45 | Github Mirror
51 | 52 |
53 | 54 | 55 |
56 | 57 |
58 | 59 | 60 | 61 | 66 | 67 | 68 |
69 | 70 |
71 | 72 | -------------------------------------------------------------------------------- /xss/README.md: -------------------------------------------------------------------------------- 1 | ## XSS testing 2 | 3 | The pages in this dir try to smuggle script execution into the stuff 4 | rendered in your browser in various ways. 5 | 6 | Unfortunately there are a LOT of ways modern browsers will let you do 7 | that. Here we try a bunch of them and see if we are successful in 8 | defeating them with our preprocessing. The attack methods came from 9 | 10 | https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet 11 | 12 | This page is checking showdown rendering. 13 | 14 | Any successful script executions pop up an alert box. Seeing some strange 15 | renderings just talking about scripts on this page and no popups counts as 16 | a success... 17 | 18 | 19 | https://something.com 20 | https://something.com' onmouseover='alert("xss/README.md: onmouseover in anonymous link")' 21 | https://something.com" onmouseover="alert('xss/README.md: onmouseover in anonymous link')" 22 | https://something.com#' onmouseover='alert("xss/README.md: onmouseover in anonymous link")' 23 | https://something.com#" onmouseover="alert('xss/README.md: onmouseover in anonymous link')" 24 | https://something.com?x=' onmouseover='alert("xss/README.md: onmouseover in anonymous link")' 25 | https://something.com?x=" onmouseover="alert('xss/README.md: onmouseover in anonymous link')" 26 | https://something.com" onmouseover='alert("xss/README.md: onmouseover in anonymous link")" 27 | https://something.com" onmouseover='alert("xss/README.md: onmouseover in anonymous link")" 28 | https://something.com&#34 onmouseover='alert("xss/README.md: onmouseover in anonymous link")" 29 | 30 | 31 | 32 | x click me 34 | 35 | ## 36 | ## javascript:alert('xss/README.md: js: in header') 37 | 38 | javascript:/*--> 39 | 40 | 41 | 42 | 43 | 44 | "> 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | < 65 | 71 | 72 | 73 | 74 | 75 |
  • XSS
    76 | 77 | 78 | Set.constructor`alert\x28document.domain\x29``` 79 | 80 | 81 | 82 | 83 |
    84 | 85 | 86 | 87 | 88 | ¼script¾alert(¢XSS¢)¼/script¾ 89 | 90 | 91 | 92 | 93 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | "> 106 | 107 | 108 | +ADw-SCRIPT+AD4-alert('XSS');+ADw-/SCRIPT+AD4- 109 | 110 | 111 | 112 | %3Cscript>alert("xss"); 113 | <script>alert("xss"); 114 | <script>alert("xss"); 115 | <script>alert("xss"); 116 | <script>alert("xss"); 117 | <script>alert("xss"); 118 | <script>alert("xss"); 119 | <script>alert("xss"); 120 | <script>alert("xss"); 121 | <script>alert("xss"); 122 | <script>alert("xss"); 123 | <script>alert("xss"); 124 | <script>alert("xss"); 125 | <script>alert("xss"); 126 | <script>alert("xss"); 127 | <script>alert("xss"); 128 | <script>alert("xss"); 129 | <script>alert("xss"); 130 | <script>alert("xss"); 131 | <script>alert("xss"); 132 | <script>alert("xss"); 133 | <script>alert("xss"); 134 | <script>alert("xss"); 135 | <script>alert("xss"); 136 | <script>alert("xss"); 137 | <script>alert("xss"); 138 | <script>alert("xss"); 139 | <script>alert("xss"); 140 | <script>alert("xss"); 141 | <script>alert("xss"); 142 | <script>alert("xss"); 143 | <script>alert("xss"); 144 | <script>alert("xss"); 145 | <script>alert("xss"); 146 | <script>alert("xss"); 147 | <script>alert("xss"); 148 | <script>alert("xss"); 149 | <script>alert("xss"); 150 | <script>alert("xss"); 151 | <script>alert("xss"); 152 | <script>alert("xss"); 153 | <script>alert("xss"); 154 | <script>alert("xss"); 155 | <script>alert("xss"); 156 | <script>alert("xss"); 157 | <script>alert("xss"); 158 | <script>alert("xss"); 159 | <script>alert("xss"); 160 | <script>alert("xss"); 161 | <script>alert("xss"); 162 | <script>alert("xss"); 163 | <script>alert("xss"); 164 | <script>alert("xss"); 165 | <script>alert("xss"); 166 | <script>alert("xss"); 167 | <script>alert("xss"); 168 | <script>alert("xss"); 169 | <script>alert("xss"); 170 | <script>alert("xss"); 171 | <script>alert("xss"); 172 | <script>alert("xss"); 173 | <script>alert("xss"); 174 | <script>alert("xss"); 175 | <script>alert("xss"); 176 | <script>alert("xss"); 177 | \x3cscript>alert("xss"); 178 | \x3Cscript>alert("xss"); 179 | \u003cscript>alert("xss"); 180 | \u003Cscript>alert("xss"); 181 | 182 | -------------------------------------------------------------------------------- /xss/xss.c: -------------------------------------------------------------------------------- 1 | ## XSS testing 2 | 3 | The pages in this dir try to smuggle script execution into the stuff 4 | rendered in your browser in various ways. 5 | 6 | Unfortunately there are a LOT of ways modern browsers will let you do 7 | that. Here we try a bunch of them and see if we are successful in 8 | defeating them. The attack methods came from 9 | 10 | https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet 11 | 12 | You should not get any alert popups on this page. 13 | 14 | This page is checking highlight.js rendering for C. 15 | 16 | <<< 17 | 18 | https://" onmouseover="" 19 | 20 | 21 | x click me 23 | 24 | ## 25 | ## javascript:alert('xss/README.md: js: in header') 26 | 27 | javascript:/*--> 28 | 29 | 30 | 31 | 32 | 33 | "> 34 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | < 54 | 60 | 61 | 62 | 63 | 64 |
    • XSS
      65 | 66 | 67 | Set.constructor`alert\x28document.domain\x29``` 68 | 69 | 70 | 71 | 72 |
      73 | 74 | 75 | 76 | 77 | ¼script¾alert(¢XSS¢)¼/script¾ 78 | 79 | 80 | 81 | 82 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | "> 95 | 96 | 97 | +ADw-SCRIPT+AD4-alert('XSS');+ADw-/SCRIPT+AD4- 98 | 99 | 100 | 101 | %3Cscript>alert("xss"); 102 | <script>alert("xss"); 103 | <script>alert("xss"); 104 | <script>alert("xss"); 105 | <script>alert("xss"); 106 | <script>alert("xss"); 107 | <script>alert("xss"); 108 | <script>alert("xss"); 109 | <script>alert("xss"); 110 | <script>alert("xss"); 111 | <script>alert("xss"); 112 | <script>alert("xss"); 113 | <script>alert("xss"); 114 | <script>alert("xss"); 115 | <script>alert("xss"); 116 | <script>alert("xss"); 117 | <script>alert("xss"); 118 | <script>alert("xss"); 119 | <script>alert("xss"); 120 | <script>alert("xss"); 121 | <script>alert("xss"); 122 | <script>alert("xss"); 123 | <script>alert("xss"); 124 | <script>alert("xss"); 125 | <script>alert("xss"); 126 | <script>alert("xss"); 127 | <script>alert("xss"); 128 | <script>alert("xss"); 129 | <script>alert("xss"); 130 | <script>alert("xss"); 131 | <script>alert("xss"); 132 | <script>alert("xss"); 133 | <script>alert("xss"); 134 | <script>alert("xss"); 135 | <script>alert("xss"); 136 | <script>alert("xss"); 137 | <script>alert("xss"); 138 | <script>alert("xss"); 139 | <script>alert("xss"); 140 | <script>alert("xss"); 141 | <script>alert("xss"); 142 | <script>alert("xss"); 143 | <script>alert("xss"); 144 | <script>alert("xss"); 145 | <script>alert("xss"); 146 | <script>alert("xss"); 147 | <script>alert("xss"); 148 | <script>alert("xss"); 149 | <script>alert("xss"); 150 | <script>alert("xss"); 151 | <script>alert("xss"); 152 | <script>alert("xss"); 153 | <script>alert("xss"); 154 | <script>alert("xss"); 155 | <script>alert("xss"); 156 | <script>alert("xss"); 157 | <script>alert("xss"); 158 | <script>alert("xss"); 159 | <script>alert("xss"); 160 | <script>alert("xss"); 161 | <script>alert("xss"); 162 | <script>alert("xss"); 163 | <script>alert("xss"); 164 | <script>alert("xss"); 165 | <script>alert("xss"); 166 | \x3cscript>alert("xss"); 167 | \x3Cscript>alert("xss"); 168 | \u003cscript>alert("xss"); 169 | \u003Cscript>alert("xss"); 170 | 171 | --------------------------------------------------------------------------------