Skip to content

Commit

Permalink
Begin Migrating ClaroDocs to Closure Templates Rather Than Manual Str…
Browse files Browse the repository at this point in the history
…ingBuilder

The entire motivation for this is to have some confidence that the html generated can't become the target of XSS vulnerabilities since I'm generating html based on user given strings in .claro_module_api files (most egregiously via the doc comments I'll start parsing for each exported item). This extra headache that I'm going through is important because the design of ClaroDocs is built around the idea that ALL dep modules, including 3rd party deps, have their docs inlined into the docs for any given claro binary. By doing this extra work to ensure that everything is properly escaped, users should be able to blindly host ClaroDocs anywhere and be confident that there's no potential vulnerability.

There're still more pieces to migrate, and unfortunately this will never be particularly clean since the public Soy (Closure) Templates don't support using Protos in the java backend at the moment...
  • Loading branch information
JasonSteving99 committed Oct 8, 2023
1 parent bfa8c47 commit eb1d5ae
Show file tree
Hide file tree
Showing 18 changed files with 794 additions and 212 deletions.
16 changes: 16 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ http_file(
url = "https://github.com/JasonSteving99/claro-lang/releases/download/v0.1.271/claro-cli-install.tar.gz",
)

# ClaroDocs is built atop Google's Closure Templates in order to ensure that I'm not generating unsafe html since the
# intention is for users to be able to trust and host ClaroDocs themselves (particularly relevant since ClaroDocs
# automatically generate inlined docs for all of the binaries dependencies, whether first or 3rd party).
http_archive(
name = "io_bazel_rules_closure",
sha256 = "9498e57368efb82b985db1ed426a767cbf1ba0398fd7aed632fc3908654e1b1e",
strip_prefix = "rules_closure-0.12.0",
urls = [
"https://github.com/bazelbuild/rules_closure/archive/0.12.0.tar.gz",
],
)

load("@io_bazel_rules_closure//closure:repositories.bzl", "rules_closure_dependencies", "rules_closure_toolchains")
rules_closure_dependencies()
rules_closure_toolchains()

# See this documentation to understand how fetching Maven deps works in Bazel:
# https://github.com/bazelbuild/rules_jvm_external
# When you add a new maven dep run the following command to update new deps:
Expand Down
15 changes: 15 additions & 0 deletions src/java/com/claro/module_system/clarodocs/html_rendering/BUILD
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
load("@io_bazel_rules_closure//closure:defs.bzl", "closure_java_template_library")

closure_java_template_library(
name = "html_soy",
srcs = [
"code_block.soy",
"procedures.soy",
"tokens.soy",
"types.soy",
"utils.soy",
],
java_package = "com.claro.module_system.clarodocs.html_rendering",
visibility = ["//src/java/com/claro/module_system/clarodocs/html_rendering:__subpackages__"],
)

java_library(
name = "util",
srcs = ["Util.java"],
deps = [":html_soy"],
visibility = ["//src/java/com/claro/module_system/clarodocs/html_rendering:__subpackages__"],
)
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
package com.claro.module_system.clarodocs.html_rendering;

import com.google.template.soy.SoyFileSet;
import com.google.template.soy.tofu.SoyTofu;

import java.util.Arrays;
import java.util.stream.Collectors;

import static com.claro.module_system.clarodocs.html_rendering.Util.CssClass.*;

// TODO(steving) Migrate to pre-compiled SoySauce templates instead of slower (hence deprecated) SoyTofu templates.
@SuppressWarnings("deprecation")
public class Util {
public static final SoyTofu SOY =
SoyFileSet.builder()
.add(Util.class.getResource("code_block.soy"))
.add(Util.class.getResource("procedures.soy"))
.add(Util.class.getResource("tokens.soy"))
.add(Util.class.getResource("types.soy"))
.add(Util.class.getResource("utils.soy"))
.build()
.compileToTofu();

public enum CssClass {
TOKEN_GROUP_1("tokenGroup1"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public static void renderAliasHtml(
res.append(String.format(
Util.wrapAsDefaultCodeBlock(ALIAS_DEF_CLASS_NAME, aliasName, ALIAS_DEF_TEMPLATE),
aliasName,
TypeHtml.renderType(new StringBuilder(), wrappedType)
TypeHtml.renderTypeHtml(wrappedType)
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{namespace codeblock}


{template .code}
{@param codeContent: html}
{@param class: string}
{@param id: string}
<pre>
<code class="{$class}" id="{$id}">
{$codeContent}
</code>
</pre>
{/template}
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,19 @@ public static void renderContractDefHtml(
contractDef.getName().substring(0, contractDef.getName().indexOf('$')),
Joiner.on(", ").join(contractDef.getTypeParamNamesList()),
contractDef.getSignaturesList().stream()
.map(proc -> ProcedureHtml.generateProcedureHtmlWithIndentationLevel(proc, 1))
.map(ProcedureHtml::generateProcedureHtml)
.collect(Collectors.joining("\n"))
));
}

public static void renderContractImplHtml(
StringBuilder res, SerializedClaroModule.ExportedContractImplementation contractImpl) {
String implementedContractName = contractImpl.getImplementedContractName();
StringBuilder placeholder = new StringBuilder();
res.append(String.format(
Util.wrapAsDefaultCodeBlock(CONTRACT_IMPL_CLASS_NAME, implementedContractName, CONTRACT_IMPL_TEMPLATE),
implementedContractName.substring(0, implementedContractName.indexOf('$')),
contractImpl.getConcreteTypeParamsList().stream()
.map(t -> {
String typeHtml = TypeHtml.renderType(placeholder, Types.parseTypeProto(t)).toString();
placeholder.setLength(0);
return typeHtml;
})
.map(t -> TypeHtml.renderTypeHtml(Types.parseTypeProto(t)).toString())
.collect(Collectors.joining(", "))
));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,27 @@ public static String renderHomePage(
" color: #f8f8f2;\n" +
" overflow-x: scroll;\n" +
"}\n" +
".procedure-def {\n" +
" margin-left: 30px;" +
"}\n" +
".initializers {\n" +
" margin-left: 30px;" +
"}\n" +
".unwrappers {\n" +
" margin-left: 30px;" +
"}\n" +
".contract-def {\n" +
" margin-left: 30px;" +
"}\n" +
".initializers .procedure-def {\n" +
" margin-left: 60px;" +
"}\n" +
".unwrappers .procedure-def {\n" +
" margin-left: 60px;" +
"}\n" +
".contract-def .procedure-def {\n" +
" margin-left: 60px;" +
"}\n" +
"\n" +
".tj_container {\n" +
" height: 100%;\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ java_library(
name = "initializers",
srcs = ["InitializersHtml.java"],
deps = [
"//:guava",
"//src/java/com/claro/module_system/clarodocs/html_rendering:html_soy",
"//src/java/com/claro/module_system/clarodocs/html_rendering:util",
"//src/java/com/claro/module_system/clarodocs/html_rendering/procedures:procedure_html",
"//src/java/com/claro/module_system/module_serialization/proto:serialized_claro_module_java_proto",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
import com.claro.module_system.clarodocs.html_rendering.Util;
import com.claro.module_system.clarodocs.html_rendering.procedures.ProcedureHtml;
import com.claro.module_system.module_serialization.proto.SerializedClaroModule;
import com.google.template.soy.tofu.SoyTofu;

import java.util.stream.Collectors;

import static com.claro.module_system.clarodocs.html_rendering.Util.GrammarPart.INITIALIZERS;

// TODO(steving) Migrate to pre-compiled SoySauce templates instead of slower (hence deprecated) SoyTofu templates.
@SuppressWarnings("deprecation")
public class InitializersHtml {
private static final SoyTofu.Renderer INITIALIZERS_TEMPLATE =
Util.SOY.newRenderer("initializers.initializers");
public static final String INITIALIZERS_CLASS = "initializers";
public static final String INITIALIZERS_BLOCK_TEMPLATE =
INITIALIZERS + " %s {\n%s\n}";
Expand All @@ -25,7 +30,7 @@ public static void renderInitializersBlock(
INITIALIZERS_BLOCK_TEMPLATE,
initializedTypeName,
procedures.getProceduresList().stream()
.map(procedure -> ProcedureHtml.generateProcedureHtmlWithIndentationLevel(procedure, 1))
.map(ProcedureHtml::generateProcedureHtml)
.collect(Collectors.joining("\n"))
)
));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{namespace procedures}


{template .exportedProcedure}
{@param name: string}
{@param? requiredContracts: list<[contractName: string, genericTypeParams: list<string>]>}
{@param? genericTypeParams: list<string>}
{@param? argTypes: list<html>}
{@param? outputType: html}

{call codeblock.code}
{param codeContent kind="html"}
{call .procedureSignature data="all" /}
{/param}
{param class kind="text"}{call .procedureDefClass /}{/param}
{param id: $name /}
{/call}
{/template}

{template .procedureDefClass kind="text" visibility="private"}
procedure-def
{/template}

{template .requires visibility="private"}
{@param requiredContracts: list<[contractName: string, genericTypeParams: list<string>]>}
{call tokens.REQUIRES /}(
{for $contract in $requiredContracts}
{$contract.contractName}{call tokens.LT /}
{for $genericTypeParam, $i in $contract.genericTypeParams}
{if $i > 0}, {/if} // Comma after all but first.
{$genericTypeParam}
{/for}
{call tokens.GT /}
{/for}
)
{/template}

{template .args visibility="private"}
{@param argTypes: list<html>}
{for $arg, $i in $argTypes}
{if $i > 0}, {/if}
arg{$i}{call tokens.COLON /} {$arg}
{/for}
{/template}

{template .procedureSignature visibility="private"}
{@param name: string}
{@param? requiredContracts: list<[contractName: string, genericTypeParams: list<string>]>}
{@param? genericTypeParams: list<string>}
{@param? argTypes: list<html>}
{@param? outputType: html}

{if $requiredContracts}
{call .requires}
{param requiredContracts: $requiredContracts /}
{/call}<br>
{/if}
{if $argTypes}
{if $outputType}
{call tokens.FUNCTION /}
{else}
{call tokens.CONSUMER /}
{/if}
{else}
{call tokens.PROVIDER /}
{/if}
{sp}{$name}
{if $genericTypeParams}
{call tokens.LT /}
{for $genericTypeParam, $i in $genericTypeParams}
{if $i > 0}, {/if}
{$genericTypeParam}
{/for}
{call tokens.GT /}
{/if}
(
{if $argTypes}
{call .args}
{param argTypes: $argTypes /}
{/call}
{/if}
)
{if $outputType}
{sp}{call tokens.ARROW /}{sp}{$outputType}
{/if}
{call tokens.SEMICOLON /}
{/template}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ java_library(
name = "procedure_html",
srcs = ["ProcedureHtml.java"],
deps = [
"//:guava",
"//src/java/com/claro/intermediate_representation/types:types",
"//src/java/com/claro/module_system/clarodocs/html_rendering:html_soy",
"//src/java/com/claro/module_system/clarodocs/html_rendering:util",
"//src/java/com/claro/module_system/clarodocs/html_rendering/typedefs:type_html",
"//src/java/com/claro/module_system/module_serialization/proto:serialized_claro_module_java_proto",
Expand Down
Loading

0 comments on commit eb1d5ae

Please sign in to comment.