finish up translations and setup doc server stuff
This commit is contained in:
225
doc/.clang-format
Normal file
225
doc/.clang-format
Normal file
@ -0,0 +1,225 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignArrayOfStructures: Left
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: MultiLine
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: false
|
||||
BinPackParameters: true
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakAfterAttributes: Never
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 120
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: false
|
||||
InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Right
|
||||
PPIndentWidth: -1
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: true
|
||||
RemoveBracesLLVM: false
|
||||
RemoveSemicolon: false
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: false
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: LexicographicNumeric
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
WhitespaceSensitiveMacros:
|
||||
- BOOST_PP_STRINGIZE
|
||||
- CF_SWIFT_NAME
|
||||
- NS_SWIFT_NAME
|
||||
- PP_STRINGIZE
|
||||
- STRINGIZE
|
||||
...
|
||||
|
4
doc/.gitignore
vendored
Normal file
4
doc/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
compile_commands.json
|
||||
.cache
|
||||
*.elf
|
||||
dist
|
35
doc/Makefile
Normal file
35
doc/Makefile
Normal file
@ -0,0 +1,35 @@
|
||||
# dirs
|
||||
DIRS = $(shell find src/* -type d)
|
||||
DISTDIR = dist
|
||||
OUTDIRS = $(patsubst src/%,$(DISTDIR)/%,$(DIRS))
|
||||
|
||||
# sources
|
||||
HSRCS = $(wildcard inc/*.h)
|
||||
CSRCS = $(shell find -type f -name '*.c')
|
||||
OBJS = $(patsubst ./src/%.c,./$(DISTDIR)/%.o,$(CSRCS))
|
||||
|
||||
# compiler flags
|
||||
CFLAGS = -O3 -fstack-protector-strong -fcf-protection=full -fstack-clash-protection
|
||||
LIBS = -lctorm -lcmark
|
||||
INCLUDE = -I./inc
|
||||
|
||||
all: doc.elf
|
||||
|
||||
doc.elf: $(OBJS)
|
||||
echo $(OBJS) $(OUTDIRS)
|
||||
gcc $(CFLAGS) -o $@ $^ $(LIBS)
|
||||
|
||||
$(DISTDIR)/%.o: src/%.c
|
||||
@mkdir -pv $(OUTDIRS)
|
||||
gcc $(CFLAGS) $(INCLUDE) -c -o $@ $^ $(LIBS)
|
||||
|
||||
format:
|
||||
clang-format -i -style=file $(CSRCS) $(HSRCS)
|
||||
|
||||
clean:
|
||||
rm -rf $(DISTDIR)
|
||||
|
||||
run:
|
||||
./doc.elf
|
||||
|
||||
.PHONY: format clean run
|
178
doc/docs/en/api.md
Normal file
178
doc/docs/en/api.md
Normal file
@ -0,0 +1,178 @@
|
||||
<!-- This is the markdown file that will be served by the index route -->
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
|
||||
# [{{.api.Host}}]({{.api.String}})
|
||||
This is the API for my personal website, [{{.frontend.Host}}]({{.frontend.String}}).
|
||||
|
||||
It stores information about the self-hosted services I provide and it also allows me
|
||||
to publish news and updates about these services using an Atom feed. It's written in
|
||||
Go and uses SQLite for storage. Licensed under GNU GPL version 3.
|
||||
|
||||
**Source code and the license is available at**: [https://github.com/ngn13/website](https://github.com/ngn13/website)
|
||||
**You can report issues to**: [https://github.com/ngn13/website/issues](https://github.com/ngn13/website/issues)
|
||||
|
||||
The rest of this document contains documentation for all the available API endpoints.
|
||||
|
||||
## Version 1 Endpoints
|
||||
Each version 1 endpoint, can be accessed using the /v1 route.
|
||||
|
||||
All the endpoints return JSON formatted data.
|
||||
|
||||
### Errors
|
||||
If any error occurs, you will get a non-200 response. And the JSON data will have an
|
||||
"error" key, which will contain information about the error that occured, in the
|
||||
string format. This is the only JSON key that will be set in non-200 responses.
|
||||
|
||||
### Results
|
||||
If no error occurs, "error" key will be set to an emtpy string (""). If the endpoint
|
||||
returns any data, this will be stored using the "result" key. The "result" have a
|
||||
different expected type and a format for each endpoint.
|
||||
|
||||
### Multilang
|
||||
Some "result" formats may use a structure called "Multilang". This is a simple JSON
|
||||
structure that includes one key for each supported language. The key is named after
|
||||
the language it represents. Currently only supported languages are:
|
||||
- English (`en`)
|
||||
- Turkish (`tr`)
|
||||
|
||||
So each multilang structure, will have **at least** one of these keys.
|
||||
|
||||
Here is an example multilang structure:
|
||||
```
|
||||
{
|
||||
"en": "Hello, world!",
|
||||
"tr": "Merhaba, dünya!"
|
||||
}
|
||||
```
|
||||
If a "result" field is using a multilang structure, it will be specified as "Multilang"
|
||||
in the rest of the documentation.
|
||||
|
||||
### Administrator routes
|
||||
The endpoints under the "/v1/admin" route, are administrator-only routes. To access
|
||||
these routes you'll need to specfiy and password using the "Authorization" header.
|
||||
If the password you specify, matches with the password specified using the
|
||||
`API_PASSWORD` environment variable, you will be able to access the route.
|
||||
|
||||
### GET /v1/services
|
||||
Returns a list of available services. Each service has the following JSON format:
|
||||
```
|
||||
{
|
||||
"name": "Test Service",
|
||||
"desc": {
|
||||
"en": "Service used for testing the API",
|
||||
"tr": "API'ı test etmek için kullanılan servis"
|
||||
},
|
||||
"check_time": 1735861944,
|
||||
"check_res": 1,
|
||||
"check_url": "http://localhost:7001",
|
||||
"clear": "http://localhost:7001",
|
||||
"onion": "",
|
||||
"i2p": ""
|
||||
}
|
||||
```
|
||||
Where:
|
||||
- `name`: Service name (string)
|
||||
- `desc`: Service description (Multilang)
|
||||
- `check_time`: Last time status check time for the service, set 0 if status checking is
|
||||
not supported for this service/status checking is disabled (integer, UNIX timestamp)
|
||||
- `check_res`: Last service status check result (integer)
|
||||
* 0 if the service is down
|
||||
* 1 if the service is up
|
||||
* 2 if the service is up, but slow
|
||||
* 3 if the service doesn't support status checking/status checking is disabled
|
||||
- `check_url`: URL used for service's status check (string, empty if none)
|
||||
- `clear`: Clearnet URL for the service (string, empty string if none)
|
||||
- `onion`: Onion (TOR) URL for the service (string, empty string if none)
|
||||
- `i2p`: I2P URL for the service (string, empty string if none)
|
||||
|
||||
You can also get information about a specific service by specifying it's name using
|
||||
a URL query named "name".
|
||||
|
||||
### GET /v1/news/:language
|
||||
Returns a Atom feed of news for the given language. Supports languages that are supported
|
||||
by Multilang.
|
||||
|
||||
### GET /v1/metrics
|
||||
Returns metrics about the API usage. The metric data has the following format:
|
||||
```
|
||||
{
|
||||
"number":8,
|
||||
"since":1736294400,
|
||||
"total":8
|
||||
}
|
||||
```
|
||||
Where:
|
||||
- `number`: Visitor number of the the current visitor (integer)
|
||||
- `since`: Metric collection start date (integer, UNIX timestamp)
|
||||
- `total`: Total number of visitors (integer)
|
||||
|
||||
Note that visitor number may change after a certain amount of requests by other clients,
|
||||
if the client wants to preserve it's visitor number, it should save it somewhere.
|
||||
|
||||
### GET /v1/admin/logs
|
||||
Returns a list of administrator logs. Each log has the following JSON format:
|
||||
```
|
||||
{
|
||||
"action": "Added service \"Test Service\"",
|
||||
"time": 1735861794
|
||||
}
|
||||
```
|
||||
Where:
|
||||
- `action`: Action that the administrator performed (string)
|
||||
- `time`: Time when the administrator action was performed (integer, UNIX timestamp)
|
||||
|
||||
Client can get the logs for only a single address, by setting the URL query "addr".
|
||||
|
||||
### PUT /v1/admin/service/add
|
||||
Creates a new service. The request body needs to contain JSON data, and it needs to
|
||||
have the JSON format used to represent a service. See "/v1/services/all" route to
|
||||
see this format.
|
||||
|
||||
Returns no data on success.
|
||||
|
||||
### DELETE /v1/admin/service/del
|
||||
Deletes a service. The client needs to specify the name of the service to delete, by
|
||||
setting the URL query "name".
|
||||
|
||||
Returns no data on success.
|
||||
|
||||
### GET /v1/admin/service/check
|
||||
Forces a status check for all the services.
|
||||
|
||||
Returns no data on success.
|
||||
|
||||
### PUT /v1/admin/news/add
|
||||
Creates a news post. The request body needs to contain JSON data, and it needs
|
||||
to use the following JSON format:
|
||||
```
|
||||
{
|
||||
"id": "test_news",
|
||||
"title": {
|
||||
"en": "Very important news",
|
||||
"tr": "Çok önemli haber"
|
||||
},
|
||||
"author": "ngn",
|
||||
"content": {
|
||||
"en": "Just letting you know that I'm testing the API",
|
||||
"tr": "Sadece API'ı test ettiğimi bilmenizi istedim"
|
||||
}
|
||||
}
|
||||
```
|
||||
Where:
|
||||
- `id`: Unique ID for the news post (string)
|
||||
- `title`: Title for the news post (Multilang)
|
||||
- `author`: Author of the news post (string)
|
||||
- `content`: Contents of the news post (Multilang)
|
||||
|
||||
Returns no data on success.
|
||||
|
||||
### DELETE /v1/admin/news/del
|
||||
Deletes a news post. The client needs to specify the ID of the news post to delete,
|
||||
by setting the URL query "id".
|
||||
|
||||
Returns no data on success.
|
18
doc/inc/config.h
Normal file
18
doc/inc/config.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct option {
|
||||
const char *name;
|
||||
char *value;
|
||||
bool required;
|
||||
} option_t;
|
||||
|
||||
typedef struct config {
|
||||
option_t *options;
|
||||
int32_t count;
|
||||
} config_t;
|
||||
|
||||
int32_t config_load(config_t *conf);
|
||||
char *config_get(config_t *conf, const char *name);
|
4
doc/inc/routes.h
Normal file
4
doc/inc/routes.h
Normal file
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include <ctorm/all.h>
|
||||
|
||||
void GET_read(req_t *req, res_t *res);
|
39
doc/src/config.c
Normal file
39
doc/src/config.c
Normal file
@ -0,0 +1,39 @@
|
||||
#include <ctorm/log.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "config.h"
|
||||
|
||||
option_t options[] = {
|
||||
{"host", "0.0.0.0:7003", true }, // host the server should listen on
|
||||
{NULL, NULL, false},
|
||||
};
|
||||
|
||||
int32_t config_load(config_t *conf) {
|
||||
bzero(conf, sizeof(*conf));
|
||||
|
||||
char *value = NULL;
|
||||
conf->options = options;
|
||||
|
||||
for (option_t *opt = conf->options; opt->name != NULL; opt++) {
|
||||
if ((value = getenv(opt->name)) != NULL)
|
||||
opt->value = value;
|
||||
|
||||
if (opt->required && *opt->value == 0) {
|
||||
error("please specify a value for the required config option %s", opt->name);
|
||||
errno = EFAULT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
conf->count++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *config_get(config_t *conf, const char *name) {
|
||||
for (int32_t i = 0; i < conf->count; i++)
|
||||
if (strcmp(conf->options[i].name, name) == 0)
|
||||
return conf->options[i].value;
|
||||
return NULL;
|
||||
}
|
25
doc/src/main.c
Normal file
25
doc/src/main.c
Normal file
@ -0,0 +1,25 @@
|
||||
#include <ctorm/all.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "routes.h"
|
||||
#include "config.h"
|
||||
|
||||
int main() {
|
||||
config_t conf;
|
||||
app_config_t config;
|
||||
|
||||
if (config_load(&conf) < 0)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
app_config_new(&config);
|
||||
config.disable_logging = true;
|
||||
|
||||
app_t *app = app_new(&config);
|
||||
GET(app, "/read", GET_read);
|
||||
|
||||
if (!app_run(app, config_get(&conf, "host")))
|
||||
error("failed to start the app: %s", app_geterror());
|
||||
|
||||
app_free(app);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
118
doc/src/routes/read.c
Normal file
118
doc/src/routes/read.c
Normal file
@ -0,0 +1,118 @@
|
||||
#include <linux/limits.h>
|
||||
#include <ctorm/all.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <cmark.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "routes.h"
|
||||
|
||||
void GET_read(req_t *req, res_t *res) {
|
||||
const char *lang = REQ_QUERY("lang");
|
||||
const char *name = REQ_QUERY("name");
|
||||
|
||||
char fp[PATH_MAX + 1], md[NAME_MAX + 1];
|
||||
struct dirent *dirent = NULL;
|
||||
int32_t name_len = 0, ent_len = 0;
|
||||
DIR *dir = NULL;
|
||||
|
||||
bzero(fp, sizeof(fp));
|
||||
bzero(md, sizeof(md));
|
||||
|
||||
if (NULL == name)
|
||||
return RES_REDIRECT("/");
|
||||
|
||||
name_len = strlen(name);
|
||||
|
||||
if (NULL == lang)
|
||||
lang = "en";
|
||||
|
||||
if (NULL == (dir = opendir("docs"))) {
|
||||
error("failed to open the docs dir: %s", strerror(errno));
|
||||
RES_SENDFILE("html/internal.html");
|
||||
res->code = 500;
|
||||
return;
|
||||
}
|
||||
|
||||
while ((dirent = readdir(dir)) != NULL) {
|
||||
if (strncmp(dirent->d_name, lang, PATH_MAX) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
if (NULL == dirent) {
|
||||
RES_SENDFILE("html/notfound.html");
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(fp, sizeof(fp), "docs/%s", lang);
|
||||
|
||||
if (NULL == (dir = opendir(fp))) {
|
||||
error("failed to open the language dir: %s", strerror(errno));
|
||||
RES_SENDFILE("html/internal.html");
|
||||
res->code = 500;
|
||||
return;
|
||||
}
|
||||
|
||||
while ((dirent = readdir(dir)) != NULL) {
|
||||
if ((ent_len = strlen(dirent->d_name) - 3) != name_len)
|
||||
continue;
|
||||
|
||||
if (strncmp(dirent->d_name, name, name_len) == 0) {
|
||||
memcpy(md, dirent->d_name, ent_len + 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
if (NULL == dirent) {
|
||||
RES_SENDFILE("html/notfound.html");
|
||||
return;
|
||||
}
|
||||
|
||||
char *md_content = NULL, md_fp[PATH_MAX + 1];
|
||||
struct stat md_st;
|
||||
int md_fd = 0;
|
||||
|
||||
snprintf(md_fp, sizeof(fp), "%s/%s", fp, md);
|
||||
|
||||
if ((md_fd = open(md_fp, O_RDONLY)) < 0) {
|
||||
error("failed to open %s: %s", fp, strerror(errno));
|
||||
goto err_internal_close;
|
||||
}
|
||||
|
||||
if (fstat(md_fd, &md_st) < 0) {
|
||||
error("failed to fstat %s: %s", fp, strerror(errno));
|
||||
goto err_internal_close;
|
||||
}
|
||||
|
||||
if ((md_content = mmap(0, md_st.st_size, PROT_READ, MAP_PRIVATE, md_fd, 0)) == NULL) {
|
||||
error("failed to mmap %s: %s", fp, strerror(errno));
|
||||
goto err_internal_close;
|
||||
}
|
||||
|
||||
char *parsed = cmark_markdown_to_html(md_content, md_st.st_size, CMARK_OPT_DEFAULT);
|
||||
RES_SEND(parsed);
|
||||
|
||||
free(parsed);
|
||||
munmap(md_content, md_st.st_size);
|
||||
close(md_fd);
|
||||
|
||||
return;
|
||||
|
||||
err_internal_close:
|
||||
close(md_fd);
|
||||
|
||||
RES_SENDFILE("html/internal.html");
|
||||
return;
|
||||
}
|
Reference in New Issue
Block a user