Tie all submit endpoints and image upload endpoints together.

pull/1/head
sigonasr2 4 years ago
parent 0785506fad
commit 32d3ceeaf3
  1. 2
      frontend/public/index.html
  2. 45
      frontend/src/App.css
  3. 32
      frontend/src/App.js
  4. 14
      frontend/src/setupProxy.js
  5. 22
      imgparser/HELP.md
  6. 24
      imgparser/build.gradle
  7. BIN
      imgparser/build/classes/java/main/com/example/demo/Controller.class
  8. BIN
      imgparser/build/classes/java/main/com/example/demo/DemoApplication.class
  9. 1
      imgparser/build/resources/main/application.properties
  10. BIN
      imgparser/gradle/wrapper/gradle-wrapper.jar
  11. 5
      imgparser/gradle/wrapper/gradle-wrapper.properties
  12. 185
      imgparser/gradlew
  13. 104
      imgparser/gradlew.bat
  14. 1
      imgparser/projectDivaImgParser
  15. 1
      imgparser/settings.gradle
  16. 19
      imgparser/src/main/java/com/example/Controller.java
  17. 14
      imgparser/src/main/java/com/example/demo/Controller.java
  18. 13
      imgparser/src/main/java/com/example/demo/DemoApplication.java
  19. 1
      imgparser/src/main/resources/application.properties
  20. 13
      imgparser/src/test/java/com/example/demo/DemoApplicationTests.java
  21. 3
      server/.gitignore
  22. 49
      server/app.js
  23. 16
      server/node_modules/busboy/.travis.yml
  24. 19
      server/node_modules/busboy/LICENSE
  25. 222
      server/node_modules/busboy/README.md
  26. 73
      server/node_modules/busboy/deps/encoding/encoding-indexes.js
  27. 2391
      server/node_modules/busboy/deps/encoding/encoding.js
  28. 88
      server/node_modules/busboy/lib/main.js
  29. 325
      server/node_modules/busboy/lib/types/multipart.js
  30. 214
      server/node_modules/busboy/lib/types/urlencoded.js
  31. 172
      server/node_modules/busboy/lib/utils.js
  32. 64
      server/node_modules/busboy/package.json
  33. 80
      server/node_modules/busboy/test/test-types-multipart-stream-pause.js
  34. 343
      server/node_modules/busboy/test/test-types-multipart.js
  35. 183
      server/node_modules/busboy/test/test-types-urlencoded.js
  36. 66
      server/node_modules/busboy/test/test-utils-decoder.js
  37. 96
      server/node_modules/busboy/test/test-utils-parse-params.js
  38. 4
      server/node_modules/busboy/test/test.js
  39. 16
      server/node_modules/dicer/.travis.yml
  40. 19
      server/node_modules/dicer/LICENSE
  41. 122
      server/node_modules/dicer/README.md
  42. 63
      server/node_modules/dicer/bench/dicer-bench-multipart-parser.js
  43. 70
      server/node_modules/dicer/bench/formidable-bench-multipart-parser.js
  44. 56
      server/node_modules/dicer/bench/multipartser-bench-multipart-parser.js
  45. 76
      server/node_modules/dicer/bench/multiparty-bench-multipart-parser.js
  46. 63
      server/node_modules/dicer/bench/parted-bench-multipart-parser.js
  47. 485
      server/node_modules/dicer/bench/parted-multipart.js
  48. 239
      server/node_modules/dicer/lib/Dicer.js
  49. 110
      server/node_modules/dicer/lib/HeaderParser.js
  50. 11
      server/node_modules/dicer/lib/PartStream.js
  51. 66
      server/node_modules/dicer/package.json
  52. 31
      server/node_modules/dicer/test/fixtures/many-noend/original
  53. 1
      server/node_modules/dicer/test/fixtures/many-noend/part1
  54. 1
      server/node_modules/dicer/test/fixtures/many-noend/part1.header
  55. 0
      server/node_modules/dicer/test/fixtures/many-noend/part2
  56. 1
      server/node_modules/dicer/test/fixtures/many-noend/part2.header
  57. 0
      server/node_modules/dicer/test/fixtures/many-noend/part3
  58. 1
      server/node_modules/dicer/test/fixtures/many-noend/part3.header
  59. 0
      server/node_modules/dicer/test/fixtures/many-noend/part4
  60. 1
      server/node_modules/dicer/test/fixtures/many-noend/part4.header
  61. 3
      server/node_modules/dicer/test/fixtures/many-noend/part5
  62. 1
      server/node_modules/dicer/test/fixtures/many-noend/part5.header
  63. 1
      server/node_modules/dicer/test/fixtures/many-noend/part6
  64. 1
      server/node_modules/dicer/test/fixtures/many-noend/part6.header
  65. 2
      server/node_modules/dicer/test/fixtures/many-noend/part7.header
  66. 32
      server/node_modules/dicer/test/fixtures/many-wrongboundary/original
  67. 33
      server/node_modules/dicer/test/fixtures/many-wrongboundary/preamble
  68. 1
      server/node_modules/dicer/test/fixtures/many-wrongboundary/preamble.error
  69. 32
      server/node_modules/dicer/test/fixtures/many/original
  70. 1
      server/node_modules/dicer/test/fixtures/many/part1
  71. 1
      server/node_modules/dicer/test/fixtures/many/part1.header
  72. 0
      server/node_modules/dicer/test/fixtures/many/part2
  73. 1
      server/node_modules/dicer/test/fixtures/many/part2.header
  74. 0
      server/node_modules/dicer/test/fixtures/many/part3
  75. 1
      server/node_modules/dicer/test/fixtures/many/part3.header
  76. 0
      server/node_modules/dicer/test/fixtures/many/part4
  77. 1
      server/node_modules/dicer/test/fixtures/many/part4.header
  78. 3
      server/node_modules/dicer/test/fixtures/many/part5
  79. 1
      server/node_modules/dicer/test/fixtures/many/part5.header
  80. 0
      server/node_modules/dicer/test/fixtures/many/part6
  81. 2
      server/node_modules/dicer/test/fixtures/many/part6.header
  82. 1
      server/node_modules/dicer/test/fixtures/many/part7
  83. 1
      server/node_modules/dicer/test/fixtures/many/part7.header
  84. 24
      server/node_modules/dicer/test/fixtures/nested-full/original
  85. 1
      server/node_modules/dicer/test/fixtures/nested-full/part1
  86. 1
      server/node_modules/dicer/test/fixtures/nested-full/part1.header
  87. 12
      server/node_modules/dicer/test/fixtures/nested-full/part2
  88. 2
      server/node_modules/dicer/test/fixtures/nested-full/part2.header
  89. 2
      server/node_modules/dicer/test/fixtures/nested-full/preamble.header
  90. 21
      server/node_modules/dicer/test/fixtures/nested/original
  91. 1
      server/node_modules/dicer/test/fixtures/nested/part1
  92. 1
      server/node_modules/dicer/test/fixtures/nested/part1.header
  93. 12
      server/node_modules/dicer/test/fixtures/nested/part2
  94. 2
      server/node_modules/dicer/test/fixtures/nested/part2.header
  95. 87
      server/node_modules/dicer/test/test-endfinish.js
  96. 68
      server/node_modules/dicer/test/test-headerparser.js
  97. 148
      server/node_modules/dicer/test/test-multipart-extra-trailer.js
  98. 228
      server/node_modules/dicer/test/test-multipart-nolisteners.js
  99. 240
      server/node_modules/dicer/test/test-multipart.js
  100. 4
      server/node_modules/dicer/test/test.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -18,6 +18,8 @@
<link href="https://fonts.googleapis.com/css2?family=M+PLUS+1p:wght@300;400&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Open+Sans+Condensed:wght@700&family=Open+Sans:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.

@ -98,3 +98,48 @@ background: linear-gradient(180deg, rgba(251,251,251,1) 0%, rgba(240,255,255,1)
transform: rotate(360deg);
}
}
.files input {
outline: 2px dashed #92b0b3;
outline-offset: -10px;
-webkit-transition: outline-offset .15s ease-in-out, background-color .15s linear;
transition: outline-offset .15s ease-in-out, background-color .15s linear;
padding: 120px 0px 85px 35%;
text-align: center !important;
margin: 0;
width: 100% !important;
}
.files input:focus{ outline: 2px dashed #92b0b3; outline-offset: -10px;
-webkit-transition: outline-offset .15s ease-in-out, background-color .15s linear;
transition: outline-offset .15s ease-in-out, background-color .15s linear; border:1px solid #92b0b3;
}
.files{ position:relative}
.files:after { pointer-events: none;
position: absolute;
top: 140px;
left: 0;
width: 50px;
right: 0;
height: 56px;
content: "";
background-image: url(https://image.flaticon.com/icons/png/128/109/109612.png);
display: block;
margin: 0 auto;
background-size: 100%;
background-repeat: no-repeat;
}
.color input{ background-color:#f0f0ff;}
.files:before {
position: absolute;
bottom: 10px;
left: 0; pointer-events: none;
width: 100%;
right: 0;
height: 72px;
content: " or drag it here. ";
display: block;
margin: 0 auto;
color: #000000;
text-transform: capitalize;
text-align: center;
}

@ -275,6 +275,35 @@ function Rankings(){
);
}
function Submit() {
var [file,setFile] = useState(null);
var prepFile = (e)=>{
setFile(e.currentTarget.files[0])
}
var uploadFile = (e)=>{
const data = new FormData()
data.append('file', file)
/*data.append("username","sigonasr2");
data.append("authentication_token","sig");*/
axios.post("http://projectdivar.com/upload", data, {})
.then(res => {
console.log(res.statusText)
})
.catch((err)=>{console.log(err.message)})
}
return (<form method="post" action="#" id="#">
<div className="form-group color files">
<h3>Submit your play</h3>
<i>Plays can be a single image or a bunch of images in a zip file!</i>
<hr/>
<input type="file" name="file" className="form-control" onChange={(e)=>{prepFile(e)}}/>
<button type="button" className="btn btn-primary btn-block" onClick={(e)=>{uploadFile(e)}}>Upload</button>
</div>
</form>);
}
function App() {
return (<Router>
<div className="container content">
@ -303,6 +332,9 @@ function App() {
<Route path="/user/:username">
<Profile/>
</Route>
<Route path="/submitplay">
<Submit/>
</Route>
<Route path="/">
<h1 className="title">Project DivaR</h1>
Under construction!

@ -8,6 +8,13 @@ module.exports = function(app) {
changeOrigin: true,
})
);
app.use(
"/upload",
createProxyMiddleware({
target: 'http://server:4501',
changeOrigin: true,
})
);
app.use(
"/song/:songname",
createProxyMiddleware({
@ -113,4 +120,11 @@ module.exports = function(app) {
changeOrigin: true,
})
);
app.use(
"/files",
createProxyMiddleware({
target: 'http://server:4501',
changeOrigin: true,
})
);
};

@ -1,22 +0,0 @@
# Getting Started
### Reference Documentation
For further reference, please consider the following sections:
* [Official Gradle documentation](https://docs.gradle.org)
* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/gradle-plugin/reference/html/)
* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/gradle-plugin/reference/html/#build-image)
* [Spring Web](https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications)
### Guides
The following guides illustrate how to use some features concretely:
* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
* [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/)
### Additional Links
These additional references should also help you:
* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle)

@ -1,24 +0,0 @@
plugins {
id 'org.springframework.boot' version '2.3.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}

Binary file not shown.

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
imgparser/gradlew vendored

@ -1,185 +0,0 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

@ -1,104 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -0,0 +1 @@
Subproject commit c23231e281c8d55f275be235158f326244e1c445

@ -1 +0,0 @@
rootProject.name = 'demo'

@ -1,19 +0,0 @@
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.HashMap;
@RestController
public class Controller {
@GetMapping("/image")
public HashMap<String,String> helloWorld(@RequestParam("url") String url){
HashMap<String,String> map = new HashMap<>();
map.put("test1",Integer.toString(1));
map.put("test2","Hello World");
return map;
}
}

@ -1,14 +0,0 @@
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Controller {
@GetMapping("/")
public String helloWorld() {
return "Hello from Spring!";
}
}

@ -1,13 +0,0 @@
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

@ -1,13 +0,0 @@
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
}

3
server/.gitignore vendored

@ -1 +1,2 @@
.env.twitter
.env.twitter
files

@ -12,6 +12,12 @@ app.use(
extended: true,
})
)
const fileUpload = require('express-fileupload');
app.use(
fileUpload({createParentPath:true,
safeFileNames: true, preserveExtension: true})
)
let allowCrossDomain = function(req, res, next) {
@ -77,6 +83,29 @@ app.delete('/remove',(req,res)=>{
}
})
app.post('/upload', function(req, res) {
if (!req.files || Object.keys(req.files).length === 0 || req.body.username===undefined || req.body.authentication_token===undefined) {
return res.status(400).send('No files were uploaded. Invalid parameters.');
}
let file = req.files.file;
var uploads = 0;
var userId = -1;
var fileLoc = "";
db.query("select uploads,id from users where username=$1",[req.body.username])
.then((data)=>{uploads=data.rows[0].uploads;
userId=data.rows[0].id;
fileLoc = "files/plays/"+userId+"/"+uploads;
return file.mv(fileLoc);
})
.then((data)=>{return db.query("update users set uploads=$1 where username=$2",[Number(uploads)+1,req.body.username])})
.then((data)=>{return axios.post("http://projectdivar.com/image",{url:"http://projectdivar.com/"+fileLoc,user:req.body.username,auth:req.body.authentication_token})})
.then((data)=>{res.status(200).send(data.data)})
.catch((err)=>{res.status(500).send(err.message)})
});
app.post('/submit', (req, res) => {
if (req.body &&
req.body.username!==undefined && req.body.authentication_token!==undefined && req.body.song!==undefined && req.body.difficulty!==undefined && req.body.cool!==undefined && req.body.fine!==undefined && req.body.safe!==undefined && req.body.sad!==undefined && req.body.worst!==undefined && req.body.percent!==undefined) {
@ -88,7 +117,18 @@ app.post('/submit', (req, res) => {
if (!(req.body.difficulty==="H"||req.body.difficulty==="N"||req.body.difficulty==="E"||req.body.difficulty==="EX"||req.body.difficulty==="EXEX"))
{throw new Error("Invalid difficulty!")}
var songsubmitdata={},isFC=false,songRating=-1,userId = -1,songId=-1,playcount=-1,fccount=-1,cool=-1,fine=-1,safe=-1,sad=-1,worst=-1,alreadyPassed=false,eclear=-1,nclear=-1,hclear=-1,exclear=-1,exexclear=-1;
var songsubmitdata={},mod="",combo=-1,gameScore=-1,isFC=false,songRating=-1,userId = -1,songId=-1,playcount=-1,fccount=-1,cool=-1,fine=-1,safe=-1,sad=-1,worst=-1,alreadyPassed=false,eclear=-1,nclear=-1,hclear=-1,exclear=-1,exexclear=-1;
if (req.body.mod!==undefined) {
mod = req.body.mod;
}
if (req.body.combo!==undefined) {
combo = req.body.combo;
}
if (req.body.gameScore!==undefined) {
gameScore = req.body.gameScore;
}
db.query("select id,authentication_token,playcount,fccount,cool,fine,safe,sad,worst,eclear,nclear,hclear,exclear,exexclear from users where username=$1 limit 1",[req.body.username])
.then((data)=>{if(data && data.rows.length>0){if (data.rows[0].authentication_token===req.body.authentication_token){
var obj = data.rows[0];
@ -98,11 +138,11 @@ app.post('/submit', (req, res) => {
})
.then((data)=>{if(data && data.rows.length>0){songId=data.rows[0].id; return db.query('select rating from songdata where songid=$1 and difficulty=$2 limit 1',[songId,req.body.difficulty])}else{throw new Error("Could not find song.")}})
.then((data)=>{songRating=data.rows[0].rating;return db.query("select id from plays where userid=$1 and score>0 and difficulty=$2 and songid=$3 limit 1",[userId,req.body.difficulty,songId])})
.then((data)=>{if(data && data.rows.length>0){alreadyPassed=true;/*console.log(data);*/};var score=CalculateSongScore({rating:songRating,cool:req.body.cool,fine:req.body.fine,safe:req.body.safe,sad:req.body.sad,worst:req.body.worst,percent:req.body.percent,difficulty:req.body.difficulty,fail:fail});return db.query("insert into plays(songId,userId,difficulty,cool,fine,safe,sad,worst,percent,date,score,fail) values($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) returning *",[songId,userId,req.body.difficulty,req.body.cool,req.body.fine,req.body.safe,req.body.sad,req.body.worst,req.body.percent,new Date(),score,fail])})
.then((data)=>{if(data && data.rows.length>0){alreadyPassed=true;/*console.log(data);*/};var score=CalculateSongScore({rating:songRating,cool:req.body.cool,fine:req.body.fine,safe:req.body.safe,sad:req.body.sad,worst:req.body.worst,percent:req.body.percent,difficulty:req.body.difficulty,fail:fail});return db.query("insert into plays(songId,userId,difficulty,cool,fine,safe,sad,worst,percent,date,score,fail,mod,combo,gamescore) values($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) returning *",[songId,userId,req.body.difficulty,req.body.cool,req.body.fine,req.body.safe,req.body.sad,req.body.worst,req.body.percent,new Date(),score,fail,mod,combo,gameScore])})
.then((data)=>{if(data && data.rows.length>0){
songsubmitdata = data.rows[0];
//console.log(alreadyPassed+" / "+typeof(alreadyPassed))
if(alreadyPassed===false){switch(req.body.difficulty){case"E":{eclear++}break;case"N":{nclear++}break;case"H":{hclear++}break;case"EX":{exclear++}break;case"EXEX":{exexclear++}break;}}
if(alreadyPassed===false && songsubmitdata.score>0){switch(req.body.difficulty){case"E":{eclear++}break;case"N":{nclear++}break;case"H":{hclear++}break;case"EX":{exclear++}break;case"EXEX":{exexclear++}break;}}
isFC = songsubmitdata.safe===0 && songsubmitdata.sad===0 && songsubmitdata.worst===0;
return CalculateRating(req.body.username)}else{throw new Error("Could not submit song.")}})
.then((data)=>{return db.query("update users set rating=$1,last_played=$3,playcount=$4,fccount=$5,cool=$6,fine=$7,safe=$8,sad=$9,worst=$10,eclear=$11,nclear=$12,hclear=$13,exclear=$14,exexclear=$15 where username=$2",[data,req.body.username,new Date(),++playcount,fccount+((isFC)?1:0),cool+Number(req.body.cool),fine+Number(req.body.fine),safe+Number(req.body.safe),sad+Number(req.body.sad),worst+Number(req.body.worst),eclear,nclear,hclear,exclear,exexclear])})
@ -315,6 +355,9 @@ function Process(data){
}
return "Done!";
}
app.use('/files',express.static('files'))
/*
axios.get('https://api.twitter.com/1.1/search/tweets.json?q=@divarbot', {
headers: {

@ -0,0 +1,16 @@
sudo: false
language: cpp
notifications:
email: false
env:
matrix:
- TRAVIS_NODE_VERSION="4"
- TRAVIS_NODE_VERSION="6"
- TRAVIS_NODE_VERSION="8"
- TRAVIS_NODE_VERSION="10"
install:
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION
- node --version
- npm --version
- npm install
script: npm test

@ -0,0 +1,19 @@
Copyright Brian White. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

@ -0,0 +1,222 @@
Description
===========
A node.js module for parsing incoming HTML form data.
Requirements
============
* [node.js](http://nodejs.org/) -- v4.5.0 or newer
Install
=======
npm install busboy
Examples
========
* Parsing (multipart) with default options:
```javascript
var http = require('http'),
inspect = require('util').inspect;
var Busboy = require('busboy');
http.createServer(function(req, res) {
if (req.method === 'POST') {
var busboy = new Busboy({ headers: req.headers });
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
console.log('File [' + fieldname + ']: filename: ' + filename + ', encoding: ' + encoding + ', mimetype: ' + mimetype);
file.on('data', function(data) {
console.log('File [' + fieldname + '] got ' + data.length + ' bytes');
});
file.on('end', function() {
console.log('File [' + fieldname + '] Finished');
});
});
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) {
console.log('Field [' + fieldname + ']: value: ' + inspect(val));
});
busboy.on('finish', function() {
console.log('Done parsing form!');
res.writeHead(303, { Connection: 'close', Location: '/' });
res.end();
});
req.pipe(busboy);
} else if (req.method === 'GET') {
res.writeHead(200, { Connection: 'close' });
res.end('<html><head></head><body>\
<form method="POST" enctype="multipart/form-data">\
<input type="text" name="textfield"><br />\
<input type="file" name="filefield"><br />\
<input type="submit">\
</form>\
</body></html>');
}
}).listen(8000, function() {
console.log('Listening for requests');
});
// Example output, using http://nodejs.org/images/ryan-speaker.jpg as the file:
//
// Listening for requests
// File [filefield]: filename: ryan-speaker.jpg, encoding: binary
// File [filefield] got 11971 bytes
// Field [textfield]: value: 'testing! :-)'
// File [filefield] Finished
// Done parsing form!
```
* Save all incoming files to disk:
```javascript
var http = require('http'),
path = require('path'),
os = require('os'),
fs = require('fs');
var Busboy = require('busboy');
http.createServer(function(req, res) {
if (req.method === 'POST') {
var busboy = new Busboy({ headers: req.headers });
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
var saveTo = path.join(os.tmpDir(), path.basename(fieldname));
file.pipe(fs.createWriteStream(saveTo));
});
busboy.on('finish', function() {
res.writeHead(200, { 'Connection': 'close' });
res.end("That's all folks!");
});
return req.pipe(busboy);
}
res.writeHead(404);
res.end();
}).listen(8000, function() {
console.log('Listening for requests');
});
```
* Parsing (urlencoded) with default options:
```javascript
var http = require('http'),
inspect = require('util').inspect;
var Busboy = require('busboy');
http.createServer(function(req, res) {
if (req.method === 'POST') {
var busboy = new Busboy({ headers: req.headers });
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
console.log('File [' + fieldname + ']: filename: ' + filename);
file.on('data', function(data) {
console.log('File [' + fieldname + '] got ' + data.length + ' bytes');
});
file.on('end', function() {
console.log('File [' + fieldname + '] Finished');
});
});
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) {
console.log('Field [' + fieldname + ']: value: ' + inspect(val));
});
busboy.on('finish', function() {
console.log('Done parsing form!');
res.writeHead(303, { Connection: 'close', Location: '/' });
res.end();
});
req.pipe(busboy);
} else if (req.method === 'GET') {
res.writeHead(200, { Connection: 'close' });
res.end('<html><head></head><body>\
<form method="POST">\
<input type="text" name="textfield"><br />\
<select name="selectfield">\
<option value="1">1</option>\
<option value="10">10</option>\
<option value="100">100</option>\
<option value="9001">9001</option>\
</select><br />\
<input type="checkbox" name="checkfield">Node.js rules!<br />\
<input type="submit">\
</form>\
</body></html>');
}
}).listen(8000, function() {
console.log('Listening for requests');
});
// Example output:
//
// Listening for requests
// Field [textfield]: value: 'testing! :-)'
// Field [selectfield]: value: '9001'
// Field [checkfield]: value: 'on'
// Done parsing form!
```
API
===
_Busboy_ is a _Writable_ stream
Busboy (special) events
-----------------------
* **file**(< _string_ >fieldname, < _ReadableStream_ >stream, < _string_ >filename, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new file form field found. `transferEncoding` contains the 'Content-Transfer-Encoding' value for the file stream. `mimeType` contains the 'Content-Type' value for the file stream.
* Note: if you listen for this event, you should always handle the `stream` no matter if you care about the file contents or not (e.g. you can simply just do `stream.resume();` if you want to discard the contents), otherwise the 'finish' event will never fire on the Busboy instance. However, if you don't care about **any** incoming files, you can simply not listen for the 'file' event at all and any/all files will be automatically and safely discarded (these discarded files do still count towards `files` and `parts` limits).
* If a configured file size limit was reached, `stream` will both have a boolean property `truncated` (best checked at the end of the stream) and emit a 'limit' event to notify you when this happens.
* **field**(< _string_ >fieldname, < _string_ >value, < _boolean_ >fieldnameTruncated, < _boolean_ >valueTruncated, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new non-file field found.
* **partsLimit**() - Emitted when specified `parts` limit has been reached. No more 'file' or 'field' events will be emitted.
* **filesLimit**() - Emitted when specified `files` limit has been reached. No more 'file' events will be emitted.
* **fieldsLimit**() - Emitted when specified `fields` limit has been reached. No more 'field' events will be emitted.
Busboy methods
--------------
* **(constructor)**(< _object_ >config) - Creates and returns a new Busboy instance.
* The constructor takes the following valid `config` settings:
* **headers** - _object_ - These are the HTTP headers of the incoming request, which are used by individual parsers.
* **highWaterMark** - _integer_ - highWaterMark to use for this Busboy instance (Default: WritableStream default).
* **fileHwm** - _integer_ - highWaterMark to use for file streams (Default: ReadableStream default).
* **defCharset** - _string_ - Default character set to use when one isn't defined (Default: 'utf8').
* **preservePath** - _boolean_ - If paths in the multipart 'filename' field shall be preserved. (Default: false).
* **limits** - _object_ - Various limits on incoming data. Valid properties are:
* **fieldNameSize** - _integer_ - Max field name size (in bytes) (Default: 100 bytes).
* **fieldSize** - _integer_ - Max field value size (in bytes) (Default: 1MB).
* **fields** - _integer_ - Max number of non-file fields (Default: Infinity).
* **fileSize** - _integer_ - For multipart forms, the max file size (in bytes) (Default: Infinity).
* **files** - _integer_ - For multipart forms, the max number of file fields (Default: Infinity).
* **parts** - _integer_ - For multipart forms, the max number of parts (fields + files) (Default: Infinity).
* **headerPairs** - _integer_ - For multipart forms, the max number of header key=>value pairs to parse **Default:** 2000 (same as node's http).
* The constructor can throw errors:
* **Unsupported content type: $type** - The `Content-Type` isn't one Busboy can parse.
* **Missing Content-Type** - The provided headers don't include `Content-Type` at all.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -0,0 +1,88 @@
var fs = require('fs'),
WritableStream = require('stream').Writable,
inherits = require('util').inherits;
var parseParams = require('./utils').parseParams;
function Busboy(opts) {
if (!(this instanceof Busboy))
return new Busboy(opts);
if (opts.highWaterMark !== undefined)
WritableStream.call(this, { highWaterMark: opts.highWaterMark });
else
WritableStream.call(this);
this._done = false;
this._parser = undefined;
this._finished = false;
this.opts = opts;
if (opts.headers && typeof opts.headers['content-type'] === 'string')
this.parseHeaders(opts.headers);
else
throw new Error('Missing Content-Type');
}
inherits(Busboy, WritableStream);
Busboy.prototype.emit = function(ev) {
if (ev === 'finish') {
if (!this._done) {
this._parser && this._parser.end();
return;
} else if (this._finished) {
return;
}
this._finished = true;
}
WritableStream.prototype.emit.apply(this, arguments);
};
Busboy.prototype.parseHeaders = function(headers) {
this._parser = undefined;
if (headers['content-type']) {
var parsed = parseParams(headers['content-type']),
matched, type;
for (var i = 0; i < TYPES.length; ++i) {
type = TYPES[i];
if (typeof type.detect === 'function')
matched = type.detect(parsed);
else
matched = type.detect.test(parsed[0]);
if (matched)
break;
}
if (matched) {
var cfg = {
limits: this.opts.limits,
headers: headers,
parsedConType: parsed,
highWaterMark: undefined,
fileHwm: undefined,
defCharset: undefined,
preservePath: false
};
if (this.opts.highWaterMark)
cfg.highWaterMark = this.opts.highWaterMark;
if (this.opts.fileHwm)
cfg.fileHwm = this.opts.fileHwm;
cfg.defCharset = this.opts.defCharset;
cfg.preservePath = this.opts.preservePath;
this._parser = type(this, cfg);
return;
}
}
throw new Error('Unsupported content type: ' + headers['content-type']);
};
Busboy.prototype._write = function(chunk, encoding, cb) {
if (!this._parser)
return cb(new Error('Not ready to parse. Missing Content-Type?'));
this._parser.write(chunk, cb);
};
var TYPES = [
require('./types/multipart'),
require('./types/urlencoded'),
];
module.exports = Busboy;

@ -0,0 +1,325 @@
// TODO:
// * support 1 nested multipart level
// (see second multipart example here:
// http://www.w3.org/TR/html401/interact/forms.html#didx-multipartform-data)
// * support limits.fieldNameSize
// -- this will require modifications to utils.parseParams
var ReadableStream = require('stream').Readable,
inherits = require('util').inherits;
var Dicer = require('dicer');
var parseParams = require('../utils').parseParams,
decodeText = require('../utils').decodeText,
basename = require('../utils').basename;
var RE_BOUNDARY = /^boundary$/i,
RE_FIELD = /^form-data$/i,
RE_CHARSET = /^charset$/i,
RE_FILENAME = /^filename$/i,
RE_NAME = /^name$/i;
Multipart.detect = /^multipart\/form-data/i;
function Multipart(boy, cfg) {
if (!(this instanceof Multipart))
return new Multipart(boy, cfg);
var i,
len,
self = this,
boundary,
limits = cfg.limits,
parsedConType = cfg.parsedConType || [],
defCharset = cfg.defCharset || 'utf8',
preservePath = cfg.preservePath,
fileopts = (typeof cfg.fileHwm === 'number'
? { highWaterMark: cfg.fileHwm }
: {});
for (i = 0, len = parsedConType.length; i < len; ++i) {
if (Array.isArray(parsedConType[i])
&& RE_BOUNDARY.test(parsedConType[i][0])) {
boundary = parsedConType[i][1];
break;
}
}
function checkFinished() {
if (nends === 0 && finished && !boy._done) {
finished = false;
process.nextTick(function() {
boy._done = true;
boy.emit('finish');
});
}
}
if (typeof boundary !== 'string')
throw new Error('Multipart: Boundary not found');
var fieldSizeLimit = (limits && typeof limits.fieldSize === 'number'
? limits.fieldSize
: 1 * 1024 * 1024),
fileSizeLimit = (limits && typeof limits.fileSize === 'number'
? limits.fileSize
: Infinity),
filesLimit = (limits && typeof limits.files === 'number'
? limits.files
: Infinity),
fieldsLimit = (limits && typeof limits.fields === 'number'
? limits.fields
: Infinity),
partsLimit = (limits && typeof limits.parts === 'number'
? limits.parts
: Infinity);
var nfiles = 0,
nfields = 0,
nends = 0,
curFile,
curField,
finished = false;
this._needDrain = false;
this._pause = false;
this._cb = undefined;
this._nparts = 0;
this._boy = boy;
var parserCfg = {
boundary: boundary,
maxHeaderPairs: (limits && limits.headerPairs)
};
if (fileopts.highWaterMark)
parserCfg.partHwm = fileopts.highWaterMark;
if (cfg.highWaterMark)
parserCfg.highWaterMark = cfg.highWaterMark;
this.parser = new Dicer(parserCfg);
this.parser.on('drain', function() {
self._needDrain = false;
if (self._cb && !self._pause) {
var cb = self._cb;
self._cb = undefined;
cb();
}
}).on('part', function onPart(part) {
if (++self._nparts > partsLimit) {
self.parser.removeListener('part', onPart);
self.parser.on('part', skipPart);
boy.hitPartsLimit = true;
boy.emit('partsLimit');
return skipPart(part);
}
// hack because streams2 _always_ doesn't emit 'end' until nextTick, so let
// us emit 'end' early since we know the part has ended if we are already
// seeing the next part
if (curField) {
var field = curField;
field.emit('end');
field.removeAllListeners('end');
}
part.on('header', function(header) {
var contype,
fieldname,
parsed,
charset,
encoding,
filename,
nsize = 0;
if (header['content-type']) {
parsed = parseParams(header['content-type'][0]);
if (parsed[0]) {
contype = parsed[0].toLowerCase();
for (i = 0, len = parsed.length; i < len; ++i) {
if (RE_CHARSET.test(parsed[i][0])) {
charset = parsed[i][1].toLowerCase();
break;
}
}
}
}
if (contype === undefined)
contype = 'text/plain';
if (charset === undefined)
charset = defCharset;
if (header['content-disposition']) {
parsed = parseParams(header['content-disposition'][0]);
if (!RE_FIELD.test(parsed[0]))
return skipPart(part);
for (i = 0, len = parsed.length; i < len; ++i) {
if (RE_NAME.test(parsed[i][0])) {
fieldname = decodeText(parsed[i][1], 'binary', 'utf8');
} else if (RE_FILENAME.test(parsed[i][0])) {
filename = decodeText(parsed[i][1], 'binary', 'utf8');
if (!preservePath)
filename = basename(filename);
}
}
} else
return skipPart(part);
if (header['content-transfer-encoding'])
encoding = header['content-transfer-encoding'][0].toLowerCase();
else
encoding = '7bit';
var onData,
onEnd;
if (contype === 'application/octet-stream' || filename !== undefined) {
// file/binary field
if (nfiles === filesLimit) {
if (!boy.hitFilesLimit) {
boy.hitFilesLimit = true;
boy.emit('filesLimit');
}
return skipPart(part);
}
++nfiles;
if (!boy._events.file) {
self.parser._ignore();
return;
}
++nends;
var file = new FileStream(fileopts);
curFile = file;
file.on('end', function() {
--nends;
self._pause = false;
checkFinished();
if (self._cb && !self._needDrain) {
var cb = self._cb;
self._cb = undefined;
cb();
}
});
file._read = function(n) {
if (!self._pause)
return;
self._pause = false;
if (self._cb && !self._needDrain) {
var cb = self._cb;
self._cb = undefined;
cb();
}
};
boy.emit('file', fieldname, file, filename, encoding, contype);
onData = function(data) {
if ((nsize += data.length) > fileSizeLimit) {
var extralen = (fileSizeLimit - (nsize - data.length));
if (extralen > 0)
file.push(data.slice(0, extralen));
file.emit('limit');
file.truncated = true;
part.removeAllListeners('data');
} else if (!file.push(data))
self._pause = true;
};
onEnd = function() {
curFile = undefined;
file.push(null);
};
} else {
// non-file field
if (nfields === fieldsLimit) {
if (!boy.hitFieldsLimit) {
boy.hitFieldsLimit = true;
boy.emit('fieldsLimit');
}
return skipPart(part);
}
++nfields;
++nends;
var buffer = '',
truncated = false;
curField = part;
onData = function(data) {
if ((nsize += data.length) > fieldSizeLimit) {
var extralen = (fieldSizeLimit - (nsize - data.length));
buffer += data.toString('binary', 0, extralen);
truncated = true;
part.removeAllListeners('data');
} else
buffer += data.toString('binary');
};
onEnd = function() {
curField = undefined;
if (buffer.length)
buffer = decodeText(buffer, 'binary', charset);
boy.emit('field', fieldname, buffer, false, truncated, encoding, contype);
--nends;
checkFinished();
};
}
/* As of node@2efe4ab761666 (v0.10.29+/v0.11.14+), busboy had become
broken. Streams2/streams3 is a huge black box of confusion, but
somehow overriding the sync state seems to fix things again (and still
seems to work for previous node versions).
*/
part._readableState.sync = false;
part.on('data', onData);
part.on('end', onEnd);
}).on('error', function(err) {
if (curFile)
curFile.emit('error', err);
});
}).on('error', function(err) {
boy.emit('error', err);
}).on('finish', function() {
finished = true;
checkFinished();
});
}
Multipart.prototype.write = function(chunk, cb) {
var r;
if ((r = this.parser.write(chunk)) && !this._pause)
cb();
else {
this._needDrain = !r;
this._cb = cb;
}
};
Multipart.prototype.end = function() {
var self = this;
if (this._nparts === 0 && !self._boy._done) {
process.nextTick(function() {
self._boy._done = true;
self._boy.emit('finish');
});
} else if (this.parser.writable)
this.parser.end();
};
function skipPart(part) {
part.resume();
}
function FileStream(opts) {
if (!(this instanceof FileStream))
return new FileStream(opts);
ReadableStream.call(this, opts);
this.truncated = false;
}
inherits(FileStream, ReadableStream);
FileStream.prototype._read = function(n) {};
module.exports = Multipart;

@ -0,0 +1,214 @@
var Decoder = require('../utils').Decoder,
decodeText = require('../utils').decodeText;
var RE_CHARSET = /^charset$/i;
UrlEncoded.detect = /^application\/x-www-form-urlencoded/i;
function UrlEncoded(boy, cfg) {
if (!(this instanceof UrlEncoded))
return new UrlEncoded(boy, cfg);
var limits = cfg.limits,
headers = cfg.headers,
parsedConType = cfg.parsedConType;
this.boy = boy;
this.fieldSizeLimit = (limits && typeof limits.fieldSize === 'number'
? limits.fieldSize
: 1 * 1024 * 1024);
this.fieldNameSizeLimit = (limits && typeof limits.fieldNameSize === 'number'
? limits.fieldNameSize
: 100);
this.fieldsLimit = (limits && typeof limits.fields === 'number'
? limits.fields
: Infinity);
var charset;
for (var i = 0, len = parsedConType.length; i < len; ++i) {
if (Array.isArray(parsedConType[i])
&& RE_CHARSET.test(parsedConType[i][0])) {
charset = parsedConType[i][1].toLowerCase();
break;
}
}
if (charset === undefined)
charset = cfg.defCharset || 'utf8';
this.decoder = new Decoder();
this.charset = charset;
this._fields = 0;
this._state = 'key';
this._checkingBytes = true;
this._bytesKey = 0;
this._bytesVal = 0;
this._key = '';
this._val = '';
this._keyTrunc = false;
this._valTrunc = false;
this._hitlimit = false;
}
UrlEncoded.prototype.write = function(data, cb) {
if (this._fields === this.fieldsLimit) {
if (!this.boy.hitFieldsLimit) {
this.boy.hitFieldsLimit = true;
this.boy.emit('fieldsLimit');
}
return cb();
}
var idxeq, idxamp, i, p = 0, len = data.length;
while (p < len) {
if (this._state === 'key') {
idxeq = idxamp = undefined;
for (i = p; i < len; ++i) {
if (!this._checkingBytes)
++p;
if (data[i] === 0x3D/*=*/) {
idxeq = i;
break;
} else if (data[i] === 0x26/*&*/) {
idxamp = i;
break;
}
if (this._checkingBytes && this._bytesKey === this.fieldNameSizeLimit) {
this._hitLimit = true;
break;
} else if (this._checkingBytes)
++this._bytesKey;
}
if (idxeq !== undefined) {
// key with assignment
if (idxeq > p)
this._key += this.decoder.write(data.toString('binary', p, idxeq));
this._state = 'val';
this._hitLimit = false;
this._checkingBytes = true;
this._val = '';
this._bytesVal = 0;
this._valTrunc = false;
this.decoder.reset();
p = idxeq + 1;
} else if (idxamp !== undefined) {
// key with no assignment
++this._fields;
var key, keyTrunc = this._keyTrunc;
if (idxamp > p)
key = (this._key += this.decoder.write(data.toString('binary', p, idxamp)));
else
key = this._key;
this._hitLimit = false;
this._checkingBytes = true;
this._key = '';
this._bytesKey = 0;
this._keyTrunc = false;
this.decoder.reset();
if (key.length) {
this.boy.emit('field', decodeText(key, 'binary', this.charset),
'',
keyTrunc,
false);
}
p = idxamp + 1;
if (this._fields === this.fieldsLimit)
return cb();
} else if (this._hitLimit) {
// we may not have hit the actual limit if there are encoded bytes...
if (i > p)
this._key += this.decoder.write(data.toString('binary', p, i));
p = i;
if ((this._bytesKey = this._key.length) === this.fieldNameSizeLimit) {
// yep, we actually did hit the limit
this._checkingBytes = false;
this._keyTrunc = true;
}
} else {
if (p < len)
this._key += this.decoder.write(data.toString('binary', p));
p = len;
}
} else {
idxamp = undefined;
for (i = p; i < len; ++i) {
if (!this._checkingBytes)
++p;
if (data[i] === 0x26/*&*/) {
idxamp = i;
break;
}
if (this._checkingBytes && this._bytesVal === this.fieldSizeLimit) {
this._hitLimit = true;
break;
}
else if (this._checkingBytes)
++this._bytesVal;
}
if (idxamp !== undefined) {
++this._fields;
if (idxamp > p)
this._val += this.decoder.write(data.toString('binary', p, idxamp));
this.boy.emit('field', decodeText(this._key, 'binary', this.charset),
decodeText(this._val, 'binary', this.charset),
this._keyTrunc,
this._valTrunc);
this._state = 'key';
this._hitLimit = false;
this._checkingBytes = true;
this._key = '';
this._bytesKey = 0;
this._keyTrunc = false;
this.decoder.reset();
p = idxamp + 1;
if (this._fields === this.fieldsLimit)
return cb();
} else if (this._hitLimit) {
// we may not have hit the actual limit if there are encoded bytes...
if (i > p)
this._val += this.decoder.write(data.toString('binary', p, i));
p = i;
if ((this._val === '' && this.fieldSizeLimit === 0)
|| (this._bytesVal = this._val.length) === this.fieldSizeLimit) {
// yep, we actually did hit the limit
this._checkingBytes = false;
this._valTrunc = true;
}
} else {
if (p < len)
this._val += this.decoder.write(data.toString('binary', p));
p = len;
}
}
}
cb();
};
UrlEncoded.prototype.end = function() {
if (this.boy._done)
return;
if (this._state === 'key' && this._key.length > 0) {
this.boy.emit('field', decodeText(this._key, 'binary', this.charset),
'',
this._keyTrunc,
false);
} else if (this._state === 'val') {
this.boy.emit('field', decodeText(this._key, 'binary', this.charset),
decodeText(this._val, 'binary', this.charset),
this._keyTrunc,
this._valTrunc);
}
this.boy._done = true;
this.boy.emit('finish');
};
module.exports = UrlEncoded;

@ -0,0 +1,172 @@
var jsencoding = require('../deps/encoding/encoding');
var RE_ENCODED = /%([a-fA-F0-9]{2})/g;
function encodedReplacer(match, byte) {
return String.fromCharCode(parseInt(byte, 16));
}
function parseParams(str) {
var res = [],
state = 'key',
charset = '',
inquote = false,
escaping = false,
p = 0,
tmp = '';
for (var i = 0, len = str.length; i < len; ++i) {
if (str[i] === '\\' && inquote) {
if (escaping)
escaping = false;
else {
escaping = true;
continue;
}
} else if (str[i] === '"') {
if (!escaping) {
if (inquote) {
inquote = false;
state = 'key';
} else
inquote = true;
continue;
} else
escaping = false;
} else {
if (escaping && inquote)
tmp += '\\';
escaping = false;
if ((state === 'charset' || state === 'lang') && str[i] === "'") {
if (state === 'charset') {
state = 'lang';
charset = tmp.substring(1);
} else
state = 'value';
tmp = '';
continue;
} else if (state === 'key'
&& (str[i] === '*' || str[i] === '=')
&& res.length) {
if (str[i] === '*')
state = 'charset';
else
state = 'value';
res[p] = [tmp, undefined];
tmp = '';
continue;
} else if (!inquote && str[i] === ';') {
state = 'key';
if (charset) {
if (tmp.length) {
tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer),
'binary',
charset);
}
charset = '';
}
if (res[p] === undefined)
res[p] = tmp;
else
res[p][1] = tmp;
tmp = '';
++p;
continue;
} else if (!inquote && (str[i] === ' ' || str[i] === '\t'))
continue;
}
tmp += str[i];
}
if (charset && tmp.length) {
tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer),
'binary',
charset);
}
if (res[p] === undefined) {
if (tmp)
res[p] = tmp;
} else
res[p][1] = tmp;
return res;
};
exports.parseParams = parseParams;
function decodeText(text, textEncoding, destEncoding) {
var ret;
if (text && jsencoding.encodingExists(destEncoding)) {
try {
ret = jsencoding.TextDecoder(destEncoding)
.decode(Buffer.from(text, textEncoding));
} catch(e) {}
}
return (typeof ret === 'string' ? ret : text);
}
exports.decodeText = decodeText;
var HEX = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
], RE_PLUS = /\+/g;
function Decoder() {
this.buffer = undefined;
}
Decoder.prototype.write = function(str) {
// Replace '+' with ' ' before decoding
str = str.replace(RE_PLUS, ' ');
var res = '';
var i = 0, p = 0, len = str.length;
for (; i < len; ++i) {
if (this.buffer !== undefined) {
if (!HEX[str.charCodeAt(i)]) {
res += '%' + this.buffer;
this.buffer = undefined;
--i; // retry character
} else {
this.buffer += str[i];
++p;
if (this.buffer.length === 2) {
res += String.fromCharCode(parseInt(this.buffer, 16));
this.buffer = undefined;
}
}
} else if (str[i] === '%') {
if (i > p) {
res += str.substring(p, i);
p = i;
}
this.buffer = '';
++p;
}
}
if (p < len && this.buffer === undefined)
res += str.substring(p);
return res;
};
Decoder.prototype.reset = function() {
this.buffer = undefined;
};
exports.Decoder = Decoder;
function basename(path) {
if (typeof path !== 'string')
return '';
for (var i = path.length - 1; i >= 0; --i) {
switch (path.charCodeAt(i)) {
case 0x2F: // '/'
case 0x5C: // '\'
path = path.slice(i + 1);
return (path === '..' || path === '.' ? '' : path);
}
}
return (path === '..' || path === '.' ? '' : path);
}
exports.basename = basename;

@ -0,0 +1,64 @@
{
"_from": "busboy@^0.3.1",
"_id": "busboy@0.3.1",
"_inBundle": false,
"_integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==",
"_location": "/busboy",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "busboy@^0.3.1",
"name": "busboy",
"escapedName": "busboy",
"rawSpec": "^0.3.1",
"saveSpec": null,
"fetchSpec": "^0.3.1"
},
"_requiredBy": [
"/express-fileupload"
],
"_resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz",
"_shasum": "170899274c5bf38aae27d5c62b71268cd585fd1b",
"_spec": "busboy@^0.3.1",
"_where": "/home/sigonasr2/divar/server/node_modules/express-fileupload",
"author": {
"name": "Brian White",
"email": "mscdex@mscdex.net"
},
"bugs": {
"url": "https://github.com/mscdex/busboy/issues"
},
"bundleDependencies": false,
"dependencies": {
"dicer": "0.3.0"
},
"deprecated": false,
"description": "A streaming parser for HTML form data for node.js",
"engines": {
"node": ">=4.5.0"
},
"homepage": "https://github.com/mscdex/busboy#readme",
"keywords": [
"uploads",
"forms",
"multipart",
"form-data"
],
"licenses": [
{
"type": "MIT",
"url": "http://github.com/mscdex/busboy/raw/master/LICENSE"
}
],
"main": "./lib/main",
"name": "busboy",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/mscdex/busboy.git"
},
"scripts": {
"test": "node test/test.js"
},
"version": "0.3.1"
}

@ -0,0 +1,80 @@
var Busboy = require('..');
var path = require('path');
var inspect = require('util').inspect;
var assert = require('assert');
function formDataSection(key, value) {
return Buffer.from('\r\n--' + BOUNDARY
+ '\r\nContent-Disposition: form-data; name="'
+ key + '"\r\n\r\n' + value);
}
function formDataFile(key, filename, contentType) {
return Buffer.concat([
Buffer.from('\r\n--' + BOUNDARY + '\r\n'),
Buffer.from('Content-Disposition: form-data; name="'
+ key + '"; filename="' + filename + '"\r\n'),
Buffer.from('Content-Type: ' + contentType + '\r\n\r\n'),
Buffer.allocUnsafe(100000)
]);
}
var BOUNDARY = 'u2KxIV5yF1y+xUspOQCCZopaVgeV6Jxihv35XQJmuTx8X3sh';
var reqChunks = [
Buffer.concat([
formDataFile('file', 'file.bin', 'application/octet-stream'),
formDataSection('foo', 'foo value')
]),
formDataSection('bar', 'bar value'),
Buffer.from('\r\n--' + BOUNDARY + '--\r\n')
];
var busboy = new Busboy({
headers: {
'content-type': 'multipart/form-data; boundary=' + BOUNDARY
}
});
var finishes = 0;
var results = [];
var expected = [
['file', 'file', 'file.bin', '7bit', 'application/octet-stream'],
['field', 'foo', 'foo value', false, false, '7bit', 'text/plain'],
['field', 'bar', 'bar value', false, false, '7bit', 'text/plain'],
];
busboy.on('field', function(key, val, keyTrunc, valTrunc, encoding, contype) {
results.push(['field', key, val, keyTrunc, valTrunc, encoding, contype]);
});
busboy.on('file', function(fieldname, stream, filename, encoding, mimeType) {
results.push(['file', fieldname, filename, encoding, mimeType]);
// Simulate a pipe where the destination is pausing (perhaps due to waiting
// for file system write to finish)
setTimeout(function() {
stream.resume();
}, 10);
});
busboy.on('finish', function() {
assert(finishes++ === 0, 'finish emitted multiple times');
assert.deepEqual(results.length,
expected.length,
'Parsed result count mismatch. Saw '
+ results.length
+ '. Expected: ' + expected.length);
results.forEach(function(result, i) {
assert.deepEqual(result,
expected[i],
'Result mismatch:\nParsed: ' + inspect(result)
+ '\nExpected: ' + inspect(expected[i]));
});
}).on('error', function(err) {
assert(false, 'Unexpected error: ' + err.stack);
});
reqChunks.forEach(function(buf) {
busboy.write(buf);
});
busboy.end();
process.on('exit', function() {
assert(finishes === 1, 'busboy did not finish');
});

@ -0,0 +1,343 @@
var Busboy = require('..');
var path = require('path'),
inspect = require('util').inspect,
assert = require('assert');
var EMPTY_FN = function() {};
var t = 0,
group = path.basename(__filename, '.js') + '/';
var tests = [
{ source: [
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="file_name_0"',
'',
'super alpha file',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="file_name_1"',
'',
'super beta file',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"',
'Content-Type: application/octet-stream',
'',
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"',
'Content-Type: application/octet-stream',
'',
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
].join('\r\n')
],
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
expected: [
['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'],
['field', 'file_name_1', 'super beta file', false, false, '7bit', 'text/plain'],
['file', 'upload_file_0', 1023, 0, '1k_a.dat', '7bit', 'application/octet-stream'],
['file', 'upload_file_1', 1023, 0, '1k_b.dat', '7bit', 'application/octet-stream']
],
what: 'Fields and files'
},
{ source: [
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
'Content-Disposition: form-data; name="cont"',
'',
'some random content',
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
'Content-Disposition: form-data; name="pass"',
'',
'some random pass',
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
'Content-Disposition: form-data; name="bit"',
'',
'2',
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--'
].join('\r\n')
],
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
expected: [
['field', 'cont', 'some random content', false, false, '7bit', 'text/plain'],
['field', 'pass', 'some random pass', false, false, '7bit', 'text/plain'],
['field', 'bit', '2', false, false, '7bit', 'text/plain']
],
what: 'Fields only'
},
{ source: [
''
],
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
expected: [],
what: 'No fields and no files'
},
{ source: [
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="file_name_0"',
'',
'super alpha file',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"',
'Content-Type: application/octet-stream',
'',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
].join('\r\n')
],
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
limits: {
fileSize: 13,
fieldSize: 5
},
expected: [
['field', 'file_name_0', 'super', false, true, '7bit', 'text/plain'],
['file', 'upload_file_0', 13, 2, '1k_a.dat', '7bit', 'application/octet-stream']
],
what: 'Fields and files (limits)'
},
{ source: [
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="file_name_0"',
'',
'super alpha file',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"',
'Content-Type: application/octet-stream',
'',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
].join('\r\n')
],
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
limits: {
files: 0
},
expected: [
['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain']
],
what: 'Fields and files (limits: 0 files)'
},
{ source: [
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="file_name_0"',
'',
'super alpha file',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="file_name_1"',
'',
'super beta file',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"',
'Content-Type: application/octet-stream',
'',
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"',
'Content-Type: application/octet-stream',
'',
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
].join('\r\n')
],
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
expected: [
['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'],
['field', 'file_name_1', 'super beta file', false, false, '7bit', 'text/plain'],
],
events: ['field'],
what: 'Fields and (ignored) files'
},
{ source: [
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_0"; filename="/tmp/1k_a.dat"',
'Content-Type: application/octet-stream',
'',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_1"; filename="C:\\files\\1k_b.dat"',
'Content-Type: application/octet-stream',
'',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_2"; filename="relative/1k_c.dat"',
'Content-Type: application/octet-stream',
'',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
].join('\r\n')
],
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
expected: [
['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'],
['file', 'upload_file_1', 26, 0, '1k_b.dat', '7bit', 'application/octet-stream'],
['file', 'upload_file_2', 26, 0, '1k_c.dat', '7bit', 'application/octet-stream']
],
what: 'Files with filenames containing paths'
},
{ source: [
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_0"; filename="/absolute/1k_a.dat"',
'Content-Type: application/octet-stream',
'',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_1"; filename="C:\\absolute\\1k_b.dat"',
'Content-Type: application/octet-stream',
'',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
'Content-Disposition: form-data; name="upload_file_2"; filename="relative/1k_c.dat"',
'Content-Type: application/octet-stream',
'',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--'
].join('\r\n')
],
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k',
preservePath: true,
expected: [
['file', 'upload_file_0', 26, 0, '/absolute/1k_a.dat', '7bit', 'application/octet-stream'],
['file', 'upload_file_1', 26, 0, 'C:\\absolute\\1k_b.dat', '7bit', 'application/octet-stream'],
['file', 'upload_file_2', 26, 0, 'relative/1k_c.dat', '7bit', 'application/octet-stream']
],
what: 'Paths to be preserved through the preservePath option'
},
{ source: [
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
'Content-Disposition: form-data; name="cont"',
'Content-Type: ',
'',
'some random content',
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
'Content-Disposition: ',
'',
'some random pass',
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--'
].join('\r\n')
],
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
expected: [
['field', 'cont', 'some random content', false, false, '7bit', 'text/plain']
],
what: 'Empty content-type and empty content-disposition'
},
{ source: [
['--asdasdasdasd\r\n',
'Content-Type: text/plain\r\n',
'Content-Disposition: form-data; name="foo"\r\n',
'\r\n',
'asd\r\n',
'--asdasdasdasd--'
].join(':)')
],
boundary: 'asdasdasdasd',
expected: [],
shouldError: 'Unexpected end of multipart data',
what: 'Stopped mid-header'
},
{ source: [
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY',
'Content-Disposition: form-data; name="cont"',
'Content-Type: application/json',
'',
'{}',
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--',
].join('\r\n')
],
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
expected: [
['field', 'cont', '{}', false, false, '7bit', 'application/json']
],
what: 'content-type for fields'
},
{ source: [
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n'
],
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY',
expected: [],
what: 'empty form'
}
];
function next() {
if (t === tests.length)
return;
var v = tests[t];
var busboy = new Busboy({
limits: v.limits,
preservePath: v.preservePath,
headers: {
'content-type': 'multipart/form-data; boundary=' + v.boundary
}
}),
finishes = 0,
results = [];
if (v.events === undefined || v.events.indexOf('field') > -1) {
busboy.on('field', function(key, val, keyTrunc, valTrunc, encoding, contype) {
results.push(['field', key, val, keyTrunc, valTrunc, encoding, contype]);
});
}
if (v.events === undefined || v.events.indexOf('file') > -1) {
busboy.on('file', function(fieldname, stream, filename, encoding, mimeType) {
var nb = 0,
info = ['file',
fieldname,
nb,
0,
filename,
encoding,
mimeType];
results.push(info);
stream.on('data', function(d) {
nb += d.length;
}).on('limit', function() {
++info[3];
}).on('end', function() {
info[2] = nb;
if (stream.truncated)
++info[3];
});
});
}
busboy.on('finish', function() {
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times'));
assert.deepEqual(results.length,
v.expected.length,
makeMsg(v.what, 'Parsed result count mismatch. Saw '
+ results.length
+ '. Expected: ' + v.expected.length));
results.forEach(function(result, i) {
assert.deepEqual(result,
v.expected[i],
makeMsg(v.what,
'Result mismatch:\nParsed: ' + inspect(result)
+ '\nExpected: ' + inspect(v.expected[i]))
);
});
++t;
next();
}).on('error', function(err) {
if (!v.shouldError || v.shouldError !== err.message)
assert(false, makeMsg(v.what, 'Unexpected error: ' + err));
});
v.source.forEach(function(s) {
busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN);
});
busboy.end();
}
next();
function makeMsg(what, msg) {
return '[' + group + what + ']: ' + msg;
}
process.on('exit', function() {
assert(t === tests.length,
makeMsg('_exit',
'Only finished ' + t + '/' + tests.length + ' tests'));
});

@ -0,0 +1,183 @@
var Busboy = require('..');
var path = require('path'),
inspect = require('util').inspect,
assert = require('assert');
var EMPTY_FN = function() {};
var t = 0,
group = path.basename(__filename, '.js') + '/';
var tests = [
{ source: ['foo'],
expected: [['foo', '', false, false]],
what: 'Unassigned value'
},
{ source: ['foo=bar'],
expected: [['foo', 'bar', false, false]],
what: 'Assigned value'
},
{ source: ['foo&bar=baz'],
expected: [['foo', '', false, false],
['bar', 'baz', false, false]],
what: 'Unassigned and assigned value'
},
{ source: ['foo=bar&baz'],
expected: [['foo', 'bar', false, false],
['baz', '', false, false]],
what: 'Assigned and unassigned value'
},
{ source: ['foo=bar&baz=bla'],
expected: [['foo', 'bar', false, false],
['baz', 'bla', false, false]],
what: 'Two assigned values'
},
{ source: ['foo&bar'],
expected: [['foo', '', false, false],
['bar', '', false, false]],
what: 'Two unassigned values'
},
{ source: ['foo&bar&'],
expected: [['foo', '', false, false],
['bar', '', false, false]],
what: 'Two unassigned values and ampersand'
},
{ source: ['foo=bar+baz%2Bquux'],
expected: [['foo', 'bar baz+quux', false, false]],
what: 'Assigned value with (plus) space'
},
{ source: ['foo=bar%20baz%21'],
expected: [['foo', 'bar baz!', false, false]],
what: 'Assigned value with encoded bytes'
},
{ source: ['foo%20bar=baz%20bla%21'],
expected: [['foo bar', 'baz bla!', false, false]],
what: 'Assigned value with encoded bytes #2'
},
{ source: ['foo=bar%20baz%21&num=1000'],
expected: [['foo', 'bar baz!', false, false],
['num', '1000', false, false]],
what: 'Two assigned values, one with encoded bytes'
},
{ source: ['foo=bar&baz=bla'],
expected: [],
what: 'Limits: zero fields',
limits: { fields: 0 }
},
{ source: ['foo=bar&baz=bla'],
expected: [['foo', 'bar', false, false]],
what: 'Limits: one field',
limits: { fields: 1 }
},
{ source: ['foo=bar&baz=bla'],
expected: [['foo', 'bar', false, false],
['baz', 'bla', false, false]],
what: 'Limits: field part lengths match limits',
limits: { fieldNameSize: 3, fieldSize: 3 }
},
{ source: ['foo=bar&baz=bla'],
expected: [['fo', 'bar', true, false],
['ba', 'bla', true, false]],
what: 'Limits: truncated field name',
limits: { fieldNameSize: 2 }
},
{ source: ['foo=bar&baz=bla'],
expected: [['foo', 'ba', false, true],
['baz', 'bl', false, true]],
what: 'Limits: truncated field value',
limits: { fieldSize: 2 }
},
{ source: ['foo=bar&baz=bla'],
expected: [['fo', 'ba', true, true],
['ba', 'bl', true, true]],
what: 'Limits: truncated field name and value',
limits: { fieldNameSize: 2, fieldSize: 2 }
},
{ source: ['foo=bar&baz=bla'],
expected: [['fo', '', true, true],
['ba', '', true, true]],
what: 'Limits: truncated field name and zero value limit',
limits: { fieldNameSize: 2, fieldSize: 0 }
},
{ source: ['foo=bar&baz=bla'],
expected: [['', '', true, true],
['', '', true, true]],
what: 'Limits: truncated zero field name and zero value limit',
limits: { fieldNameSize: 0, fieldSize: 0 }
},
{ source: ['&'],
expected: [],
what: 'Ampersand'
},
{ source: ['&&&&&'],
expected: [],
what: 'Many ampersands'
},
{ source: ['='],
expected: [['', '', false, false]],
what: 'Assigned value, empty name and value'
},
{ source: [''],
expected: [],
what: 'Nothing'
},
];
function next() {
if (t === tests.length)
return;
var v = tests[t];
var busboy = new Busboy({
limits: v.limits,
headers: {
'content-type': 'application/x-www-form-urlencoded; charset=utf-8'
}
}),
finishes = 0,
results = [];
busboy.on('field', function(key, val, keyTrunc, valTrunc) {
results.push([key, val, keyTrunc, valTrunc]);
});
busboy.on('file', function() {
throw new Error(makeMsg(v.what, 'Unexpected file'));
});
busboy.on('finish', function() {
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times'));
assert.deepEqual(results.length,
v.expected.length,
makeMsg(v.what, 'Parsed result count mismatch. Saw '
+ results.length
+ '. Expected: ' + v.expected.length));
var i = 0;
results.forEach(function(result) {
assert.deepEqual(result,
v.expected[i],
makeMsg(v.what,
'Result mismatch:\nParsed: ' + inspect(result)
+ '\nExpected: ' + inspect(v.expected[i]))
);
++i;
});
++t;
next();
});
v.source.forEach(function(s) {
busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN);
});
busboy.end();
}
next();
function makeMsg(what, msg) {
return '[' + group + what + ']: ' + msg;
}
process.on('exit', function() {
assert(t === tests.length, makeMsg('_exit', 'Only finished ' + t + '/' + tests.length + ' tests'));
});

@ -0,0 +1,66 @@
var Decoder = require('../lib/utils').Decoder;
var path = require('path'),
assert = require('assert');
var group = path.basename(__filename, '.js') + '/';
[
{ source: ['Hello world'],
expected: 'Hello world',
what: 'No encoded bytes'
},
{ source: ['Hello%20world'],
expected: 'Hello world',
what: 'One full encoded byte'
},
{ source: ['Hello%20world%21'],
expected: 'Hello world!',
what: 'Two full encoded bytes'
},
{ source: ['Hello%', '20world'],
expected: 'Hello world',
what: 'One full encoded byte split #1'
},
{ source: ['Hello%2', '0world'],
expected: 'Hello world',
what: 'One full encoded byte split #2'
},
{ source: ['Hello%20', 'world'],
expected: 'Hello world',
what: 'One full encoded byte (concat)'
},
{ source: ['Hello%2Qworld'],
expected: 'Hello%2Qworld',
what: 'Malformed encoded byte #1'
},
{ source: ['Hello%world'],
expected: 'Hello%world',
what: 'Malformed encoded byte #2'
},
{ source: ['Hello+world'],
expected: 'Hello world',
what: 'Plus to space'
},
{ source: ['Hello+world%21'],
expected: 'Hello world!',
what: 'Plus and encoded byte'
},
{ source: ['5%2B5%3D10'],
expected: '5+5=10',
what: 'Encoded plus'
},
{ source: ['5+%2B+5+%3D+10'],
expected: '5 + 5 = 10',
what: 'Spaces and encoded plus'
},
].forEach(function(v) {
var dec = new Decoder(), result = '';
v.source.forEach(function(s) {
result += dec.write(s);
});
var msg = '[' + group + v.what + ']: decoded string mismatch.\n'
+ 'Saw: ' + result + '\n'
+ 'Expected: ' + v.expected;
assert.deepEqual(result, v.expected, msg);
});

@ -0,0 +1,96 @@
var parseParams = require('../lib/utils').parseParams;
var path = require('path'),
assert = require('assert'),
inspect = require('util').inspect;
var group = path.basename(__filename, '.js') + '/';
[
{ source: 'video/ogg',
expected: ['video/ogg'],
what: 'No parameters'
},
{ source: 'video/ogg;',
expected: ['video/ogg'],
what: 'No parameters (with separator)'
},
{ source: 'video/ogg; ',
expected: ['video/ogg'],
what: 'No parameters (with separator followed by whitespace)'
},
{ source: ';video/ogg',
expected: ['', 'video/ogg'],
what: 'Empty parameter'
},
{ source: 'video/*',
expected: ['video/*'],
what: 'Subtype with asterisk'
},
{ source: 'text/plain; encoding=utf8',
expected: ['text/plain', ['encoding', 'utf8']],
what: 'Unquoted'
},
{ source: 'text/plain; encoding=',
expected: ['text/plain', ['encoding', '']],
what: 'Unquoted empty string'
},
{ source: 'text/plain; encoding="utf8"',
expected: ['text/plain', ['encoding', 'utf8']],
what: 'Quoted'
},
{ source: 'text/plain; greeting="hello \\"world\\""',
expected: ['text/plain', ['greeting', 'hello "world"']],
what: 'Quotes within quoted'
},
{ source: 'text/plain; encoding=""',
expected: ['text/plain', ['encoding', '']],
what: 'Quoted empty string'
},
{ source: 'text/plain; encoding="utf8";\t foo=bar;test',
expected: ['text/plain', ['encoding', 'utf8'], ['foo', 'bar'], 'test'],
what: 'Multiple params with various spacing'
},
{ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates",
expected: ['text/plain', ['filename', '£ rates']],
what: 'Extended parameter (RFC 5987) with language'
},
{ source: "text/plain; filename*=utf-8''%c2%a3%20and%20%e2%82%ac%20rates",
expected: ['text/plain', ['filename', '£ and € rates']],
what: 'Extended parameter (RFC 5987) without language'
},
{ source: "text/plain; filename*=utf-8''%E6%B5%8B%E8%AF%95%E6%96%87%E6%A1%A3",
expected: ['text/plain', ['filename', '测试文档']],
what: 'Extended parameter (RFC 5987) without language #2'
},
{ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates; altfilename*=utf-8''%c2%a3%20and%20%e2%82%ac%20rates",
expected: ['text/plain', ['filename', '£ rates'], ['altfilename', '£ and € rates']],
what: 'Multiple extended parameters (RFC 5987) with mixed charsets'
},
{ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates; altfilename=\"foobarbaz\"",
expected: ['text/plain', ['filename', '£ rates'], ['altfilename', 'foobarbaz']],
what: 'Mixed regular and extended parameters (RFC 5987)'
},
{ source: "text/plain; filename=\"foobarbaz\"; altfilename*=iso-8859-1'en'%A3%20rates",
expected: ['text/plain', ['filename', 'foobarbaz'], ['altfilename', '£ rates']],
what: 'Mixed regular and extended parameters (RFC 5987) #2'
},
{ source: 'text/plain; filename="C:\\folder\\test.png"',
expected: ['text/plain', ['filename', 'C:\\folder\\test.png']],
what: 'Unescaped backslashes should be considered backslashes'
},
{ source: 'text/plain; filename="John \\"Magic\\" Smith.png"',
expected: ['text/plain', ['filename', 'John "Magic" Smith.png']],
what: 'Escaped double-quotes should be considered double-quotes'
},
{ source: 'multipart/form-data; charset=utf-8; boundary=0xKhTmLbOuNdArY',
expected: ['multipart/form-data', ['charset', 'utf-8'], ['boundary', '0xKhTmLbOuNdArY']],
what: 'Multiple non-quoted parameters'
},
].forEach(function(v) {
var result = parseParams(v.source),
msg = '[' + group + v.what + ']: parsed parameters mismatch.\n'
+ 'Saw: ' + inspect(result) + '\n'
+ 'Expected: ' + inspect(v.expected);
assert.deepEqual(result, v.expected, msg);
});

@ -0,0 +1,4 @@
require('fs').readdirSync(__dirname).forEach(function(f) {
if (f.substr(0, 5) === 'test-')
require('./' + f);
});

@ -0,0 +1,16 @@
sudo: false
language: cpp
notifications:
email: false
env:
matrix:
- TRAVIS_NODE_VERSION="4"
- TRAVIS_NODE_VERSION="6"
- TRAVIS_NODE_VERSION="8"
- TRAVIS_NODE_VERSION="10"
install:
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION
- node --version
- npm --version
- npm install
script: npm test

19
server/node_modules/dicer/LICENSE generated vendored

@ -0,0 +1,19 @@
Copyright Brian White. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

122
server/node_modules/dicer/README.md generated vendored

@ -0,0 +1,122 @@
Description
===========
A very fast streaming multipart parser for node.js.
Benchmarks can be found [here](https://github.com/mscdex/dicer/wiki/Benchmarks).
Requirements
============
* [node.js](http://nodejs.org/) -- v4.5.0 or newer
Install
============
npm install dicer
Examples
========
* Parse an HTTP form upload
```javascript
var inspect = require('util').inspect,
http = require('http');
var Dicer = require('dicer');
// quick and dirty way to parse multipart boundary
var RE_BOUNDARY = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i,
HTML = Buffer.from('<html><head></head><body>\
<form method="POST" enctype="multipart/form-data">\
<input type="text" name="textfield"><br />\
<input type="file" name="filefield"><br />\
<input type="submit">\
</form>\
</body></html>'),
PORT = 8080;
http.createServer(function(req, res) {
var m;
if (req.method === 'POST'
&& req.headers['content-type']
&& (m = RE_BOUNDARY.exec(req.headers['content-type']))) {
var d = new Dicer({ boundary: m[1] || m[2] });
d.on('part', function(p) {
console.log('New part!');
p.on('header', function(header) {
for (var h in header) {
console.log('Part header: k: ' + inspect(h)
+ ', v: ' + inspect(header[h]));
}
});
p.on('data', function(data) {
console.log('Part data: ' + inspect(data.toString()));
});
p.on('end', function() {
console.log('End of part\n');
});
});
d.on('finish', function() {
console.log('End of parts');
res.writeHead(200);
res.end('Form submission successful!');
});
req.pipe(d);
} else if (req.method === 'GET' && req.url === '/') {
res.writeHead(200);
res.end(HTML);
} else {
res.writeHead(404);
res.end();
}
}).listen(PORT, function() {
console.log('Listening for requests on port ' + PORT);
});
```
API
===
_Dicer_ is a _WritableStream_
Dicer (special) events
----------------------
* **finish**() - Emitted when all parts have been parsed and the Dicer instance has been ended.
* **part**(< _PartStream_ >stream) - Emitted when a new part has been found.
* **preamble**(< _PartStream_ >stream) - Emitted for preamble if you should happen to need it (can usually be ignored).
* **trailer**(< _Buffer_ >data) - Emitted when trailing data was found after the terminating boundary (as with the preamble, this can usually be ignored too).
Dicer methods
-------------
* **(constructor)**(< _object_ >config) - Creates and returns a new Dicer instance with the following valid `config` settings:
* **boundary** - _string_ - This is the boundary used to detect the beginning of a new part.
* **headerFirst** - _boolean_ - If true, preamble header parsing will be performed first.
* **maxHeaderPairs** - _integer_ - The maximum number of header key=>value pairs to parse **Default:** 2000 (same as node's http).
* **setBoundary**(< _string_ >boundary) - _(void)_ - Sets the boundary to use for parsing and performs some initialization needed for parsing. You should only need to use this if you set `headerFirst` to true in the constructor and are parsing the boundary from the preamble header.
_PartStream_ is a _ReadableStream_
PartStream (special) events
---------------------------
* **header**(< _object_ >header) - An object containing the header for this particular part. Each property value is an _array_ of one or more string values.

@ -0,0 +1,63 @@
var assert = require('assert');
var Dicer = require('..'),
boundary = '-----------------------------168072824752491622650073',
d = new Dicer({ boundary: boundary }),
mb = 100,
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024),
callbacks =
{ partBegin: -1,
partEnd: -1,
headerField: -1,
headerValue: -1,
partData: -1,
end: -1,
};
d.on('part', function(p) {
callbacks.partBegin++;
p.on('header', function(header) {
/*for (var h in header)
console.log('Part header: k: ' + inspect(h) + ', v: ' + inspect(header[h]));*/
});
p.on('data', function(data) {
callbacks.partData++;
//console.log('Part data: ' + inspect(data.toString()));
});
p.on('end', function() {
//console.log('End of part\n');
callbacks.partEnd++;
});
});
d.on('end', function() {
//console.log('End of parts');
callbacks.end++;
});
var start = +new Date(),
nparsed = d.write(buffer),
duration = +new Date - start,
mbPerSec = (mb / (duration / 1000)).toFixed(2);
console.log(mbPerSec+' mb/sec');
//assert.equal(nparsed, buffer.length);
function createMultipartBuffer(boundary, size) {
var head =
'--'+boundary+'\r\n'
+ 'content-disposition: form-data; name="field1"\r\n'
+ '\r\n'
, tail = '\r\n--'+boundary+'--\r\n'
, buffer = Buffer.allocUnsafe(size);
buffer.write(head, 'ascii', 0);
buffer.write(tail, 'ascii', buffer.length - tail.length);
return buffer;
}
process.on('exit', function() {
/*for (var k in callbacks) {
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
}*/
});

@ -0,0 +1,70 @@
var assert = require('assert');
require('../node_modules/formidable/test/common');
var multipartParser = require('../node_modules/formidable/lib/multipart_parser'),
MultipartParser = multipartParser.MultipartParser,
parser = new MultipartParser(),
boundary = '-----------------------------168072824752491622650073',
mb = 100,
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024),
callbacks =
{ partBegin: -1,
partEnd: -1,
headerField: -1,
headerValue: -1,
partData: -1,
end: -1,
};
parser.initWithBoundary(boundary);
parser.onHeaderField = function() {
callbacks.headerField++;
};
parser.onHeaderValue = function() {
callbacks.headerValue++;
};
parser.onPartBegin = function() {
callbacks.partBegin++;
};
parser.onPartData = function() {
callbacks.partData++;
};
parser.onPartEnd = function() {
callbacks.partEnd++;
};
parser.onEnd = function() {
callbacks.end++;
};
var start = +new Date(),
nparsed = parser.write(buffer),
duration = +new Date - start,
mbPerSec = (mb / (duration / 1000)).toFixed(2);
console.log(mbPerSec+' mb/sec');
//assert.equal(nparsed, buffer.length);
function createMultipartBuffer(boundary, size) {
var head =
'--'+boundary+'\r\n'
+ 'content-disposition: form-data; name="field1"\r\n'
+ '\r\n'
, tail = '\r\n--'+boundary+'--\r\n'
, buffer = Buffer.allocUnsafe(size);
buffer.write(head, 'ascii', 0);
buffer.write(tail, 'ascii', buffer.length - tail.length);
return buffer;
}
process.on('exit', function() {
/*for (var k in callbacks) {
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
}*/
});

@ -0,0 +1,56 @@
var assert = require('assert');
var multipartser = require('multipartser'),
boundary = '-----------------------------168072824752491622650073',
parser = multipartser(),
mb = 100,
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024),
callbacks =
{ partBegin: -1,
partEnd: -1,
headerField: -1,
headerValue: -1,
partData: -1,
end: -1,
};
parser.boundary( boundary );
parser.on( 'part', function ( part ) {
});
parser.on( 'end', function () {
//console.log( 'completed parsing' );
});
parser.on( 'error', function ( error ) {
console.error( error );
});
var start = +new Date(),
nparsed = parser.data(buffer),
nend = parser.end(),
duration = +new Date - start,
mbPerSec = (mb / (duration / 1000)).toFixed(2);
console.log(mbPerSec+' mb/sec');
//assert.equal(nparsed, buffer.length);
function createMultipartBuffer(boundary, size) {
var head =
'--'+boundary+'\r\n'
+ 'content-disposition: form-data; name="field1"\r\n'
+ '\r\n'
, tail = '\r\n--'+boundary+'--\r\n'
, buffer = Buffer.allocUnsafe(size);
buffer.write(head, 'ascii', 0);
buffer.write(tail, 'ascii', buffer.length - tail.length);
return buffer;
}
process.on('exit', function() {
/*for (var k in callbacks) {
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
}*/
});

@ -0,0 +1,76 @@
var assert = require('assert'),
Form = require('multiparty').Form,
boundary = '-----------------------------168072824752491622650073',
mb = 100,
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024),
callbacks =
{ partBegin: -1,
partEnd: -1,
headerField: -1,
headerValue: -1,
partData: -1,
end: -1,
};
var form = new Form({ boundary: boundary });
hijack('onParseHeaderField', function() {
callbacks.headerField++;
});
hijack('onParseHeaderValue', function() {
callbacks.headerValue++;
});
hijack('onParsePartBegin', function() {
callbacks.partBegin++;
});
hijack('onParsePartData', function() {
callbacks.partData++;
});
hijack('onParsePartEnd', function() {
callbacks.partEnd++;
});
form.on('finish', function() {
callbacks.end++;
});
var start = new Date();
form.write(buffer, function(err) {
var duration = new Date() - start;
assert.ifError(err);
var mbPerSec = (mb / (duration / 1000)).toFixed(2);
console.log(mbPerSec+' mb/sec');
});
//assert.equal(nparsed, buffer.length);
function createMultipartBuffer(boundary, size) {
var head =
'--'+boundary+'\r\n'
+ 'content-disposition: form-data; name="field1"\r\n'
+ '\r\n'
, tail = '\r\n--'+boundary+'--\r\n'
, buffer = Buffer.allocUnsafe(size);
buffer.write(head, 'ascii', 0);
buffer.write(tail, 'ascii', buffer.length - tail.length);
return buffer;
}
process.on('exit', function() {
/*for (var k in callbacks) {
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
}*/
});
function hijack(name, fn) {
var oldFn = form[name];
form[name] = function() {
fn();
return oldFn.apply(this, arguments);
};
}

@ -0,0 +1,63 @@
// A special, edited version of the multipart parser from parted is needed here
// because otherwise it attempts to do some things above and beyond just parsing
// -- like saving to disk and whatnot
var assert = require('assert');
var Parser = require('./parted-multipart'),
boundary = '-----------------------------168072824752491622650073',
parser = new Parser('boundary=' + boundary),
mb = 100,
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024),
callbacks =
{ partBegin: -1,
partEnd: -1,
headerField: -1,
headerValue: -1,
partData: -1,
end: -1,
};
parser.on('header', function() {
//callbacks.headerField++;
});
parser.on('data', function() {
//callbacks.partBegin++;
});
parser.on('part', function() {
});
parser.on('end', function() {
//callbacks.end++;
});
var start = +new Date(),
nparsed = parser.write(buffer),
duration = +new Date - start,
mbPerSec = (mb / (duration / 1000)).toFixed(2);
console.log(mbPerSec+' mb/sec');
//assert.equal(nparsed, buffer.length);
function createMultipartBuffer(boundary, size) {
var head =
'--'+boundary+'\r\n'
+ 'content-disposition: form-data; name="field1"\r\n'
+ '\r\n'
, tail = '\r\n--'+boundary+'--\r\n'
, buffer = Buffer.allocUnsafe(size);
buffer.write(head, 'ascii', 0);
buffer.write(tail, 'ascii', buffer.length - tail.length);
return buffer;
}
process.on('exit', function() {
/*for (var k in callbacks) {
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]);
}*/
});

@ -0,0 +1,485 @@
/**
* Parted (https://github.com/chjj/parted)
* A streaming multipart state parser.
* Copyright (c) 2011, Christopher Jeffrey. (MIT Licensed)
*/
var fs = require('fs')
, path = require('path')
, EventEmitter = require('events').EventEmitter
, StringDecoder = require('string_decoder').StringDecoder
, set = require('qs').set
, each = Array.prototype.forEach;
/**
* Character Constants
*/
var DASH = '-'.charCodeAt(0)
, CR = '\r'.charCodeAt(0)
, LF = '\n'.charCodeAt(0)
, COLON = ':'.charCodeAt(0)
, SPACE = ' '.charCodeAt(0);
/**
* Parser
*/
var Parser = function(type, options) {
if (!(this instanceof Parser)) {
return new Parser(type, options);
}
EventEmitter.call(this);
this.writable = true;
this.readable = true;
this.options = options || {};
var key = grab(type, 'boundary');
if (!key) {
return this._error('No boundary key found.');
}
this.key = Buffer.allocUnsafe('\r\n--' + key);
this._key = {};
each.call(this.key, function(ch) {
this._key[ch] = true;
}, this);
this.state = 'start';
this.pending = 0;
this.written = 0;
this.writtenDisk = 0;
this.buff = Buffer.allocUnsafe(200);
this.preamble = true;
this.epilogue = false;
this._reset();
};
Parser.prototype.__proto__ = EventEmitter.prototype;
/**
* Parsing
*/
Parser.prototype.write = function(data) {
if (!this.writable
|| this.epilogue) return;
try {
this._parse(data);
} catch (e) {
this._error(e);
}
return true;
};
Parser.prototype.end = function(data) {
if (!this.writable) return;
if (data) this.write(data);
if (!this.epilogue) {
return this._error('Message underflow.');
}
return true;
};
Parser.prototype._parse = function(data) {
var i = 0
, len = data.length
, buff = this.buff
, key = this.key
, ch
, val
, j;
for (; i < len; i++) {
if (this.pos >= 200) {
return this._error('Potential buffer overflow.');
}
ch = data[i];
switch (this.state) {
case 'start':
switch (ch) {
case DASH:
this.pos = 3;
this.state = 'key';
break;
default:
break;
}
break;
case 'key':
if (this.pos === key.length) {
this.state = 'key_end';
i--;
} else if (ch !== key[this.pos]) {
if (this.preamble) {
this.state = 'start';
i--;
} else {
this.state = 'body';
val = this.pos - i;
if (val > 0) {
this._write(key.slice(0, val));
}
i--;
}
} else {
this.pos++;
}
break;
case 'key_end':
switch (ch) {
case CR:
this.state = 'key_line_end';
break;
case DASH:
this.state = 'key_dash_end';
break;
default:
return this._error('Expected CR or DASH.');
}
break;
case 'key_line_end':
switch (ch) {
case LF:
if (this.preamble) {
this.preamble = false;
} else {
this._finish();
}
this.state = 'header_name';
this.pos = 0;
break;
default:
return this._error('Expected CR.');
}
break;
case 'key_dash_end':
switch (ch) {
case DASH:
this.epilogue = true;
this._finish();
return;
default:
return this._error('Expected DASH.');
}
break;
case 'header_name':
switch (ch) {
case COLON:
this.header = buff.toString('ascii', 0, this.pos);
this.pos = 0;
this.state = 'header_val';
break;
default:
buff[this.pos++] = ch | 32;
break;
}
break;
case 'header_val':
switch (ch) {
case CR:
this.state = 'header_val_end';
break;
case SPACE:
if (this.pos === 0) {
break;
}
; // FALL-THROUGH
default:
buff[this.pos++] = ch;
break;
}
break;
case 'header_val_end':
switch (ch) {
case LF:
val = buff.toString('ascii', 0, this.pos);
this._header(this.header, val);
this.pos = 0;
this.state = 'header_end';
break;
default:
return this._error('Expected LF.');
}
break;
case 'header_end':
switch (ch) {
case CR:
this.state = 'head_end';
break;
default:
this.state = 'header_name';
i--;
break;
}
break;
case 'head_end':
switch (ch) {
case LF:
this.state = 'body';
i++;
if (i >= len) return;
data = data.slice(i);
i = -1;
len = data.length;
break;
default:
return this._error('Expected LF.');
}
break;
case 'body':
switch (ch) {
case CR:
if (i > 0) {
this._write(data.slice(0, i));
}
this.pos = 1;
this.state = 'key';
data = data.slice(i);
i = 0;
len = data.length;
break;
default:
// boyer-moore-like algorithm
// at felixge's suggestion
while ((j = i + key.length - 1) < len) {
if (this._key[data[j]]) break;
i = j;
}
break;
}
break;
}
}
if (this.state === 'body') {
this._write(data);
}
};
Parser.prototype._header = function(name, val) {
/*if (name === 'content-disposition') {
this.field = grab(val, 'name');
this.file = grab(val, 'filename');
if (this.file) {
this.data = stream(this.file, this.options.path);
} else {
this.decode = new StringDecoder('utf8');
this.data = '';
}
}*/
return this.emit('header', name, val);
};
Parser.prototype._write = function(data) {
/*if (this.data == null) {
return this._error('No disposition.');
}
if (this.file) {
this.data.write(data);
this.writtenDisk += data.length;
} else {
this.data += this.decode.write(data);
this.written += data.length;
}*/
this.emit('data', data);
};
Parser.prototype._reset = function() {
this.pos = 0;
this.decode = null;
this.field = null;
this.data = null;
this.file = null;
this.header = null;
};
Parser.prototype._error = function(err) {
this.destroy();
this.emit('error', typeof err === 'string'
? new Error(err)
: err);
};
Parser.prototype.destroy = function(err) {
this.writable = false;
this.readable = false;
this._reset();
};
Parser.prototype._finish = function() {
var self = this
, field = this.field
, data = this.data
, file = this.file
, part;
this.pending++;
this._reset();
if (data && data.path) {
part = data.path;
data.end(next);
} else {
part = data;
next();
}
function next() {
if (!self.readable) return;
self.pending--;
self.emit('part', field, part);
if (data && data.path) {
self.emit('file', field, part, file);
}
if (self.epilogue && !self.pending) {
self.emit('end');
self.destroy();
}
}
};
/**
* Uploads
*/
Parser.root = process.platform === 'win32'
? 'C:/Temp'
: '/tmp';
/**
* Middleware
*/
Parser.middleware = function(options) {
options = options || {};
return function(req, res, next) {
if (options.ensureBody) {
req.body = {};
}
if (req.method === 'GET'
|| req.method === 'HEAD'
|| req._multipart) return next();
req._multipart = true;
var type = req.headers['content-type'];
if (type) type = type.split(';')[0].trim().toLowerCase();
if (type === 'multipart/form-data') {
Parser.handle(req, res, next, options);
} else {
next();
}
};
};
/**
* Handler
*/
Parser.handle = function(req, res, next, options) {
var parser = new Parser(req.headers['content-type'], options)
, diskLimit = options.diskLimit
, limit = options.limit
, parts = {}
, files = {};
parser.on('error', function(err) {
req.destroy();
next(err);
});
parser.on('part', function(field, part) {
set(parts, field, part);
});
parser.on('file', function(field, path, name) {
set(files, field, {
path: path,
name: name,
toString: function() {
return path;
}
});
});
parser.on('data', function() {
if (this.writtenDisk > diskLimit || this.written > limit) {
this.emit('error', new Error('Overflow.'));
this.destroy();
}
});
parser.on('end', next);
req.body = parts;
req.files = files;
req.pipe(parser);
};
/**
* Helpers
*/
var isWindows = process.platform === 'win32';
var stream = function(name, dir) {
var ext = path.extname(name) || ''
, name = path.basename(name, ext) || ''
, dir = dir || Parser.root
, tag;
tag = Math.random().toString(36).substring(2);
name = name.substring(0, 200) + '.' + tag;
name = path.join(dir, name) + ext.substring(0, 6);
name = name.replace(/\0/g, '');
if (isWindows) {
name = name.replace(/[:*<>|"?]/g, '');
}
return fs.createWriteStream(name);
};
var grab = function(str, name) {
if (!str) return;
var rx = new RegExp('\\b' + name + '\\s*=\\s*("[^"]+"|\'[^\']+\'|[^;,]+)', 'i')
, cap = rx.exec(str);
if (cap) {
return cap[1].trim().replace(/^['"]|['"]$/g, '');
}
};
/**
* Expose
*/
module.exports = Parser;

@ -0,0 +1,239 @@
var WritableStream = require('stream').Writable,
inherits = require('util').inherits;
var StreamSearch = require('streamsearch');
var PartStream = require('./PartStream'),
HeaderParser = require('./HeaderParser');
var DASH = 45,
B_ONEDASH = Buffer.from('-'),
B_CRLF = Buffer.from('\r\n'),
EMPTY_FN = function() {};
function Dicer(cfg) {
if (!(this instanceof Dicer))
return new Dicer(cfg);
WritableStream.call(this, cfg);
if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string'))
throw new TypeError('Boundary required');
if (typeof cfg.boundary === 'string')
this.setBoundary(cfg.boundary);
else
this._bparser = undefined;
this._headerFirst = cfg.headerFirst;
var self = this;
this._dashes = 0;
this._parts = 0;
this._finished = false;
this._realFinish = false;
this._isPreamble = true;
this._justMatched = false;
this._firstWrite = true;
this._inHeader = true;
this._part = undefined;
this._cb = undefined;
this._ignoreData = false;
this._partOpts = (typeof cfg.partHwm === 'number'
? { highWaterMark: cfg.partHwm }
: {});
this._pause = false;
this._hparser = new HeaderParser(cfg);
this._hparser.on('header', function(header) {
self._inHeader = false;
self._part.emit('header', header);
});
}
inherits(Dicer, WritableStream);
Dicer.prototype.emit = function(ev) {
if (ev === 'finish' && !this._realFinish) {
if (!this._finished) {
var self = this;
process.nextTick(function() {
self.emit('error', new Error('Unexpected end of multipart data'));
if (self._part && !self._ignoreData) {
var type = (self._isPreamble ? 'Preamble' : 'Part');
self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data'));
self._part.push(null);
process.nextTick(function() {
self._realFinish = true;
self.emit('finish');
self._realFinish = false;
});
return;
}
self._realFinish = true;
self.emit('finish');
self._realFinish = false;
});
}
} else
WritableStream.prototype.emit.apply(this, arguments);
};
Dicer.prototype._write = function(data, encoding, cb) {
// ignore unexpected data (e.g. extra trailer data after finished)
if (!this._hparser && !this._bparser)
return cb();
if (this._headerFirst && this._isPreamble) {
if (!this._part) {
this._part = new PartStream(this._partOpts);
if (this._events.preamble)
this.emit('preamble', this._part);
else
this._ignore();
}
var r = this._hparser.push(data);
if (!this._inHeader && r !== undefined && r < data.length)
data = data.slice(r);
else
return cb();
}
// allows for "easier" testing
if (this._firstWrite) {
this._bparser.push(B_CRLF);
this._firstWrite = false;
}
this._bparser.push(data);
if (this._pause)
this._cb = cb;
else
cb();
};
Dicer.prototype.reset = function() {
this._part = undefined;
this._bparser = undefined;
this._hparser = undefined;
};
Dicer.prototype.setBoundary = function(boundary) {
var self = this;
this._bparser = new StreamSearch('\r\n--' + boundary);
this._bparser.on('info', function(isMatch, data, start, end) {
self._oninfo(isMatch, data, start, end);
});
};
Dicer.prototype._ignore = function() {
if (this._part && !this._ignoreData) {
this._ignoreData = true;
this._part.on('error', EMPTY_FN);
// we must perform some kind of read on the stream even though we are
// ignoring the data, otherwise node's Readable stream will not emit 'end'
// after pushing null to the stream
this._part.resume();
}
};
Dicer.prototype._oninfo = function(isMatch, data, start, end) {
var buf, self = this, i = 0, r, ev, shouldWriteMore = true;
if (!this._part && this._justMatched && data) {
while (this._dashes < 2 && (start + i) < end) {
if (data[start + i] === DASH) {
++i;
++this._dashes;
} else {
if (this._dashes)
buf = B_ONEDASH;
this._dashes = 0;
break;
}
}
if (this._dashes === 2) {
if ((start + i) < end && this._events.trailer)
this.emit('trailer', data.slice(start + i, end));
this.reset();
this._finished = true;
// no more parts will be added
if (self._parts === 0) {
self._realFinish = true;
self.emit('finish');
self._realFinish = false;
}
}
if (this._dashes)
return;
}
if (this._justMatched)
this._justMatched = false;
if (!this._part) {
this._part = new PartStream(this._partOpts);
this._part._read = function(n) {
self._unpause();
};
ev = this._isPreamble ? 'preamble' : 'part';
if (this._events[ev])
this.emit(ev, this._part);
else
this._ignore();
if (!this._isPreamble)
this._inHeader = true;
}
if (data && start < end && !this._ignoreData) {
if (this._isPreamble || !this._inHeader) {
if (buf)
shouldWriteMore = this._part.push(buf);
shouldWriteMore = this._part.push(data.slice(start, end));
if (!shouldWriteMore)
this._pause = true;
} else if (!this._isPreamble && this._inHeader) {
if (buf)
this._hparser.push(buf);
r = this._hparser.push(data.slice(start, end));
if (!this._inHeader && r !== undefined && r < end)
this._oninfo(false, data, start + r, end);
}
}
if (isMatch) {
this._hparser.reset();
if (this._isPreamble)
this._isPreamble = false;
else {
++this._parts;
this._part.on('end', function() {
if (--self._parts === 0) {
if (self._finished) {
self._realFinish = true;
self.emit('finish');
self._realFinish = false;
} else {
self._unpause();
}
}
});
}
this._part.push(null);
this._part = undefined;
this._ignoreData = false;
this._justMatched = true;
this._dashes = 0;
}
};
Dicer.prototype._unpause = function() {
if (!this._pause)
return;
this._pause = false;
if (this._cb) {
var cb = this._cb;
this._cb = undefined;
cb();
}
};
module.exports = Dicer;

@ -0,0 +1,110 @@
var EventEmitter = require('events').EventEmitter,
inherits = require('util').inherits;
var StreamSearch = require('streamsearch');
var B_DCRLF = Buffer.from('\r\n\r\n'),
RE_CRLF = /\r\n/g,
RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/,
MAX_HEADER_PAIRS = 2000, // from node's http.js
MAX_HEADER_SIZE = 80 * 1024; // from node's http_parser
function HeaderParser(cfg) {
EventEmitter.call(this);
var self = this;
this.nread = 0;
this.maxed = false;
this.npairs = 0;
this.maxHeaderPairs = (cfg && typeof cfg.maxHeaderPairs === 'number'
? cfg.maxHeaderPairs
: MAX_HEADER_PAIRS);
this.buffer = '';
this.header = {};
this.finished = false;
this.ss = new StreamSearch(B_DCRLF);
this.ss.on('info', function(isMatch, data, start, end) {
if (data && !self.maxed) {
if (self.nread + (end - start) > MAX_HEADER_SIZE) {
end = (MAX_HEADER_SIZE - self.nread);
self.nread = MAX_HEADER_SIZE;
} else
self.nread += (end - start);
if (self.nread === MAX_HEADER_SIZE)
self.maxed = true;
self.buffer += data.toString('binary', start, end);
}
if (isMatch)
self._finish();
});
}
inherits(HeaderParser, EventEmitter);
HeaderParser.prototype.push = function(data) {
var r = this.ss.push(data);
if (this.finished)
return r;
};
HeaderParser.prototype.reset = function() {
this.finished = false;
this.buffer = '';
this.header = {};
this.ss.reset();
};
HeaderParser.prototype._finish = function() {
if (this.buffer)
this._parseHeader();
this.ss.matches = this.ss.maxMatches;
var header = this.header;
this.header = {};
this.buffer = '';
this.finished = true;
this.nread = this.npairs = 0;
this.maxed = false;
this.emit('header', header);
};
HeaderParser.prototype._parseHeader = function() {
if (this.npairs === this.maxHeaderPairs)
return;
var lines = this.buffer.split(RE_CRLF), len = lines.length, m, h,
modded = false;
for (var i = 0; i < len; ++i) {
if (lines[i].length === 0)
continue;
if (lines[i][0] === '\t' || lines[i][0] === ' ') {
// folded header content
// RFC2822 says to just remove the CRLF and not the whitespace following
// it, so we follow the RFC and include the leading whitespace ...
this.header[h][this.header[h].length - 1] += lines[i];
} else {
m = RE_HDR.exec(lines[i]);
if (m) {
h = m[1].toLowerCase();
if (m[2]) {
if (this.header[h] === undefined)
this.header[h] = [m[2]];
else
this.header[h].push(m[2]);
} else
this.header[h] = [''];
if (++this.npairs === this.maxHeaderPairs)
break;
} else {
this.buffer = lines[i];
modded = true;
break;
}
}
}
if (!modded)
this.buffer = '';
};
module.exports = HeaderParser;

@ -0,0 +1,11 @@
var inherits = require('util').inherits,
ReadableStream = require('stream').Readable;
function PartStream(opts) {
ReadableStream.call(this, opts);
}
inherits(PartStream, ReadableStream);
PartStream.prototype._read = function(n) {};
module.exports = PartStream;

@ -0,0 +1,66 @@
{
"_from": "dicer@0.3.0",
"_id": "dicer@0.3.0",
"_inBundle": false,
"_integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==",
"_location": "/dicer",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "dicer@0.3.0",
"name": "dicer",
"escapedName": "dicer",
"rawSpec": "0.3.0",
"saveSpec": null,
"fetchSpec": "0.3.0"
},
"_requiredBy": [
"/busboy"
],
"_resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz",
"_shasum": "eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872",
"_spec": "dicer@0.3.0",
"_where": "/home/sigonasr2/divar/server/node_modules/busboy",
"author": {
"name": "Brian White",
"email": "mscdex@mscdex.net"
},
"bugs": {
"url": "https://github.com/mscdex/dicer/issues"
},
"bundleDependencies": false,
"dependencies": {
"streamsearch": "0.1.2"
},
"deprecated": false,
"description": "A very fast streaming multipart parser for node.js",
"engines": {
"node": ">=4.5.0"
},
"homepage": "https://github.com/mscdex/dicer#readme",
"keywords": [
"parser",
"parse",
"parsing",
"multipart",
"form-data",
"streaming"
],
"licenses": [
{
"type": "MIT",
"url": "http://github.com/mscdex/dicer/raw/master/LICENSE"
}
],
"main": "./lib/Dicer",
"name": "dicer",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/mscdex/dicer.git"
},
"scripts": {
"test": "node test/test.js"
},
"version": "0.3.0"
}

@ -0,0 +1,31 @@
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="_method"
put
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[blog]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[public_email]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[interests]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[bio]"
hello
"quote"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="commit"
Save
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="media"; filename=""
Content-Type: application/octet-stream

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"_method\""]}

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[blog]\""]}

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[public_email]\""]}

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[interests]\""]}

@ -0,0 +1,3 @@
hello
"quote"

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[bio]\""]}

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"commit\""]}

@ -0,0 +1,2 @@
{"content-disposition": ["form-data; name=\"media\"; filename=\"\""],
"content-type": ["application/octet-stream"]}

@ -0,0 +1,32 @@
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="_method"
put
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[blog]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[public_email]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[interests]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[bio]"
hello
"quote"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="media"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="commit"
Save
------WebKitFormBoundaryWLHCs9qmcJJoyjKR--

@ -0,0 +1,33 @@
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="_method"
put
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[blog]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[public_email]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[interests]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[bio]"
hello
"quote"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="media"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="commit"
Save
------WebKitFormBoundaryWLHCs9qmcJJoyjKR--

@ -0,0 +1 @@
Preamble terminated early due to unexpected end of multipart data

@ -0,0 +1,32 @@
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="_method"
put
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[blog]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[public_email]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[interests]"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="profile[bio]"
hello
"quote"
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="media"; filename=""
Content-Type: application/octet-stream
------WebKitFormBoundaryWLHCs9qmcJJoyjKR
Content-Disposition: form-data; name="commit"
Save
------WebKitFormBoundaryWLHCs9qmcJJoyjKR--

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"_method\""]}

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[blog]\""]}

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[public_email]\""]}

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[interests]\""]}

@ -0,0 +1,3 @@
hello
"quote"

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"profile[bio]\""]}

@ -0,0 +1,2 @@
{"content-disposition": ["form-data; name=\"media\"; filename=\"\""],
"content-type": ["application/octet-stream"]}

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"commit\""]}

@ -0,0 +1,24 @@
User-Agent: foo bar baz
Content-Type: multipart/form-data; boundary=AaB03x
--AaB03x
Content-Disposition: form-data; name="foo"
bar
--AaB03x
Content-Disposition: form-data; name="files"
Content-Type: multipart/mixed, boundary=BbC04y
--BbC04y
Content-Disposition: attachment; filename="file.txt"
Content-Type: text/plain
contents
--BbC04y
Content-Disposition: attachment; filename="flowers.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
contents
--BbC04y--
--AaB03x--

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"foo\""]}

@ -0,0 +1,12 @@
--BbC04y
Content-Disposition: attachment; filename="file.txt"
Content-Type: text/plain
contents
--BbC04y
Content-Disposition: attachment; filename="flowers.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
contents
--BbC04y--

@ -0,0 +1,2 @@
{"content-disposition": ["form-data; name=\"files\""],
"content-type": ["multipart/mixed, boundary=BbC04y"]}

@ -0,0 +1,2 @@
{"user-agent": ["foo bar baz"],
"content-type": ["multipart/form-data; boundary=AaB03x"]}

@ -0,0 +1,21 @@
--AaB03x
Content-Disposition: form-data; name="foo"
bar
--AaB03x
Content-Disposition: form-data; name="files"
Content-Type: multipart/mixed, boundary=BbC04y
--BbC04y
Content-Disposition: attachment; filename="file.txt"
Content-Type: text/plain
contents
--BbC04y
Content-Disposition: attachment; filename="flowers.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
contents
--BbC04y--
--AaB03x--

@ -0,0 +1 @@
{"content-disposition": ["form-data; name=\"foo\""]}

@ -0,0 +1,12 @@
--BbC04y
Content-Disposition: attachment; filename="file.txt"
Content-Type: text/plain
contents
--BbC04y
Content-Disposition: attachment; filename="flowers.jpg"
Content-Type: image/jpeg
Content-Transfer-Encoding: binary
contents
--BbC04y--

@ -0,0 +1,2 @@
{"content-disposition": ["form-data; name=\"files\""],
"content-type": ["multipart/mixed, boundary=BbC04y"]}

@ -0,0 +1,87 @@
var Dicer = require('..');
var assert = require('assert');
var CRLF = '\r\n';
var boundary = 'boundary';
var writeSep = '--' + boundary;
var writePart = [
writeSep,
'Content-Type: text/plain',
'Content-Length: 0'
].join(CRLF)
+ CRLF + CRLF
+ 'some data' + CRLF;
var writeEnd = '--' + CRLF;
var firedEnd = false;
var firedFinish = false;
var dicer = new Dicer({boundary: boundary});
dicer.on('part', partListener);
dicer.on('finish', finishListener);
dicer.write(writePart+writeSep);
function partListener(partReadStream) {
partReadStream.on('data', function(){});
partReadStream.on('end', partEndListener);
}
function partEndListener() {
firedEnd = true;
setImmediate(afterEnd);
}
function afterEnd() {
dicer.end(writeEnd);
setImmediate(afterWrite);
}
function finishListener() {
assert(firedEnd, 'Failed to end before finishing');
firedFinish = true;
test2();
}
function afterWrite() {
assert(firedFinish, 'Failed to finish');
}
var isPausePush = true;
var firedPauseCallback = false;
var firedPauseFinish = false;
var dicer2 = null;
function test2() {
dicer2 = new Dicer({boundary: boundary});
dicer2.on('part', pausePartListener);
dicer2.on('finish', pauseFinish);
dicer2.write(writePart+writeSep, 'utf8', pausePartCallback);
setImmediate(pauseAfterWrite);
}
function pausePartListener(partReadStream) {
partReadStream.on('data', function(){});
partReadStream.on('end', function(){});
var realPush = partReadStream.push;
partReadStream.push = function fakePush() {
realPush.apply(partReadStream, arguments);
if (!isPausePush)
return true;
isPausePush = false;
return false;
};
}
function pauseAfterWrite() {
dicer2.end(writeEnd);
setImmediate(pauseAfterEnd);
}
function pauseAfterEnd() {
assert(firedPauseCallback, 'Failed to call callback after pause');
assert(firedPauseFinish, 'Failed to finish after pause');
}
function pauseFinish() {
firedPauseFinish = true;
}
function pausePartCallback() {
firedPauseCallback = true;
}

@ -0,0 +1,68 @@
var assert = require('assert'),
path = require('path');
var HeaderParser = require('../lib/HeaderParser');
var DCRLF = '\r\n\r\n',
MAXED_BUFFER = Buffer.allocUnsafe(128 * 1024);
MAXED_BUFFER.fill(0x41); // 'A'
var group = path.basename(__filename, '.js') + '/';
[
{ source: DCRLF,
expected: {},
what: 'No header'
},
{ source: ['Content-Type:\t text/plain',
'Content-Length:0'
].join('\r\n') + DCRLF,
expected: {'content-type': [' text/plain'], 'content-length': ['0']},
what: 'Value spacing'
},
{ source: ['Content-Type:\r\n text/plain',
'Foo:\r\n bar\r\n baz',
].join('\r\n') + DCRLF,
expected: {'content-type': [' text/plain'], 'foo': [' bar baz']},
what: 'Folded values'
},
{ source: ['Content-Type:',
'Foo: ',
].join('\r\n') + DCRLF,
expected: {'content-type': [''], 'foo': ['']},
what: 'Empty values'
},
{ source: MAXED_BUFFER.toString('ascii') + DCRLF,
expected: {},
what: 'Max header size (single chunk)'
},
{ source: ['ABCDEFGHIJ', MAXED_BUFFER.toString('ascii'), DCRLF],
expected: {},
what: 'Max header size (multiple chunks #1)'
},
{ source: [MAXED_BUFFER.toString('ascii'), MAXED_BUFFER.toString('ascii'), DCRLF],
expected: {},
what: 'Max header size (multiple chunk #2)'
},
].forEach(function(v) {
var parser = new HeaderParser(),
fired = false;
parser.on('header', function(header) {
assert(!fired, makeMsg(v.what, 'Header event fired more than once'));
fired = true;
assert.deepEqual(header,
v.expected,
makeMsg(v.what, 'Parsed result mismatch'));
});
if (!Array.isArray(v.source))
v.source = [v.source];
v.source.forEach(function(s) {
parser.push(s);
});
assert(fired, makeMsg(v.what, 'Did not receive header from parser'));
});
function makeMsg(what, msg) {
return '[' + group + what + ']: ' + msg;
}

@ -0,0 +1,148 @@
var Dicer = require('..');
var assert = require('assert'),
fs = require('fs'),
path = require('path'),
inspect = require('util').inspect;
var FIXTURES_ROOT = __dirname + '/fixtures/';
var t = 0,
group = path.basename(__filename, '.js') + '/';
var tests = [
{ source: 'many',
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' },
chsize: 16,
nparts: 7,
what: 'Extra trailer data pushed after finished'
},
];
function next() {
if (t === tests.length)
return;
var v = tests[t],
fixtureBase = FIXTURES_ROOT + v.source,
fd,
n = 0,
buffer = Buffer.allocUnsafe(v.chsize),
state = { parts: [] };
fd = fs.openSync(fixtureBase + '/original', 'r');
var dicer = new Dicer(v.opts),
error,
partErrors = 0,
finishes = 0;
dicer.on('part', function(p) {
var part = {
body: undefined,
bodylen: 0,
error: undefined,
header: undefined
};
p.on('header', function(h) {
part.header = h;
}).on('data', function(data) {
// make a copy because we are using readSync which re-uses a buffer ...
var copy = Buffer.allocUnsafe(data.length);
data.copy(copy);
data = copy;
if (!part.body)
part.body = [ data ];
else
part.body.push(data);
part.bodylen += data.length;
}).on('error', function(err) {
part.error = err;
++partErrors;
}).on('end', function() {
if (part.body)
part.body = Buffer.concat(part.body, part.bodylen);
state.parts.push(part);
});
}).on('error', function(err) {
error = err;
}).on('finish', function() {
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times'));
if (v.dicerError)
assert(error !== undefined, makeMsg(v.what, 'Expected error'));
else
assert(error === undefined, makeMsg(v.what, 'Unexpected error'));
if (v.events && v.events.indexOf('part') > -1) {
assert.equal(state.parts.length,
v.nparts,
makeMsg(v.what,
'Part count mismatch:\nActual: '
+ state.parts.length
+ '\nExpected: '
+ v.nparts));
if (!v.npartErrors)
v.npartErrors = 0;
assert.equal(partErrors,
v.npartErrors,
makeMsg(v.what,
'Part errors mismatch:\nActual: '
+ partErrors
+ '\nExpected: '
+ v.npartErrors));
for (var i = 0, header, body; i < v.nparts; ++i) {
if (fs.existsSync(fixtureBase + '/part' + (i+1))) {
body = fs.readFileSync(fixtureBase + '/part' + (i+1));
if (body.length === 0)
body = undefined;
} else
body = undefined;
assert.deepEqual(state.parts[i].body,
body,
makeMsg(v.what,
'Part #' + (i+1) + ' body mismatch'));
if (fs.existsSync(fixtureBase + '/part' + (i+1) + '.header')) {
header = fs.readFileSync(fixtureBase
+ '/part' + (i+1) + '.header', 'binary');
header = JSON.parse(header);
} else
header = undefined;
assert.deepEqual(state.parts[i].header,
header,
makeMsg(v.what,
'Part #' + (i+1)
+ ' parsed header mismatch:\nActual: '
+ inspect(state.parts[i].header)
+ '\nExpected: '
+ inspect(header)));
}
}
++t;
next();
});
while (true) {
n = fs.readSync(fd, buffer, 0, buffer.length, null);
if (n === 0) {
setTimeout(function() {
dicer.write('\r\n\r\n\r\n');
dicer.end();
}, 50);
break;
}
dicer.write(n === buffer.length ? buffer : buffer.slice(0, n));
}
fs.closeSync(fd);
}
next();
function makeMsg(what, msg) {
return '[' + group + what + ']: ' + msg;
}
process.on('exit', function() {
assert(t === tests.length,
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests'));
});

@ -0,0 +1,228 @@
var Dicer = require('..');
var assert = require('assert'),
fs = require('fs'),
path = require('path'),
inspect = require('util').inspect;
var FIXTURES_ROOT = __dirname + '/fixtures/';
var t = 0,
group = path.basename(__filename, '.js') + '/';
var tests = [
{ source: 'many',
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' },
chsize: 16,
nparts: 0,
what: 'No preamble or part listeners'
},
];
function next() {
if (t === tests.length)
return;
var v = tests[t],
fixtureBase = FIXTURES_ROOT + v.source,
fd,
n = 0,
buffer = Buffer.allocUnsafe(v.chsize),
state = { done: false, parts: [], preamble: undefined };
fd = fs.openSync(fixtureBase + '/original', 'r');
var dicer = new Dicer(v.opts),
error,
partErrors = 0,
finishes = 0;
if (v.events && v.events.indexOf('preamble') > -1) {
dicer.on('preamble', function(p) {
var preamble = {
body: undefined,
bodylen: 0,
error: undefined,
header: undefined
};
p.on('header', function(h) {
preamble.header = h;
}).on('data', function(data) {
// make a copy because we are using readSync which re-uses a buffer ...
var copy = Buffer.allocUnsafe(data.length);
data.copy(copy);
data = copy;
if (!preamble.body)
preamble.body = [ data ];
else
preamble.body.push(data);
preamble.bodylen += data.length;
}).on('error', function(err) {
preamble.error = err;
}).on('end', function() {
if (preamble.body)
preamble.body = Buffer.concat(preamble.body, preamble.bodylen);
if (preamble.body || preamble.header)
state.preamble = preamble;
});
});
}
if (v.events && v.events.indexOf('part') > -1) {
dicer.on('part', function(p) {
var part = {
body: undefined,
bodylen: 0,
error: undefined,
header: undefined
};
p.on('header', function(h) {
part.header = h;
}).on('data', function(data) {
// make a copy because we are using readSync which re-uses a buffer ...
var copy = Buffer.allocUnsafe(data.length);
data.copy(copy);
data = copy;
if (!part.body)
part.body = [ data ];
else
part.body.push(data);
part.bodylen += data.length;
}).on('error', function(err) {
part.error = err;
++partErrors;
}).on('end', function() {
if (part.body)
part.body = Buffer.concat(part.body, part.bodylen);
state.parts.push(part);
});
});
}
dicer.on('error', function(err) {
error = err;
}).on('finish', function() {
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times'));
if (v.dicerError)
assert(error !== undefined, makeMsg(v.what, 'Expected error'));
else
assert(error === undefined, makeMsg(v.what, 'Unexpected error'));
if (v.events && v.events.indexOf('preamble') > -1) {
var preamble;
if (fs.existsSync(fixtureBase + '/preamble')) {
var prebody = fs.readFileSync(fixtureBase + '/preamble');
if (prebody.length) {
preamble = {
body: prebody,
bodylen: prebody.length,
error: undefined,
header: undefined
};
}
}
if (fs.existsSync(fixtureBase + '/preamble.header')) {
var prehead = JSON.parse(fs.readFileSync(fixtureBase
+ '/preamble.header', 'binary'));
if (!preamble) {
preamble = {
body: undefined,
bodylen: 0,
error: undefined,
header: prehead
};
} else
preamble.header = prehead;
}
if (fs.existsSync(fixtureBase + '/preamble.error')) {
var err = new Error(fs.readFileSync(fixtureBase
+ '/preamble.error', 'binary'));
if (!preamble) {
preamble = {
body: undefined,
bodylen: 0,
error: err,
header: undefined
};
} else
preamble.error = err;
}
assert.deepEqual(state.preamble,
preamble,
makeMsg(v.what,
'Preamble mismatch:\nActual:'
+ inspect(state.preamble)
+ '\nExpected: '
+ inspect(preamble)));
}
if (v.events && v.events.indexOf('part') > -1) {
assert.equal(state.parts.length,
v.nparts,
makeMsg(v.what,
'Part count mismatch:\nActual: '
+ state.parts.length
+ '\nExpected: '
+ v.nparts));
if (!v.npartErrors)
v.npartErrors = 0;
assert.equal(partErrors,
v.npartErrors,
makeMsg(v.what,
'Part errors mismatch:\nActual: '
+ partErrors
+ '\nExpected: '
+ v.npartErrors));
for (var i = 0, header, body; i < v.nparts; ++i) {
if (fs.existsSync(fixtureBase + '/part' + (i+1))) {
body = fs.readFileSync(fixtureBase + '/part' + (i+1));
if (body.length === 0)
body = undefined;
} else
body = undefined;
assert.deepEqual(state.parts[i].body,
body,
makeMsg(v.what,
'Part #' + (i+1) + ' body mismatch'));
if (fs.existsSync(fixtureBase + '/part' + (i+1) + '.header')) {
header = fs.readFileSync(fixtureBase
+ '/part' + (i+1) + '.header', 'binary');
header = JSON.parse(header);
} else
header = undefined;
assert.deepEqual(state.parts[i].header,
header,
makeMsg(v.what,
'Part #' + (i+1)
+ ' parsed header mismatch:\nActual: '
+ inspect(state.parts[i].header)
+ '\nExpected: '
+ inspect(header)));
}
}
++t;
next();
});
while (true) {
n = fs.readSync(fd, buffer, 0, buffer.length, null);
if (n === 0) {
dicer.end();
break;
}
dicer.write(n === buffer.length ? buffer : buffer.slice(0, n));
}
fs.closeSync(fd);
}
next();
function makeMsg(what, msg) {
return '[' + group + what + ']: ' + msg;
}
process.on('exit', function() {
assert(t === tests.length,
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests'));
});

@ -0,0 +1,240 @@
var Dicer = require('..');
var assert = require('assert'),
fs = require('fs'),
path = require('path'),
inspect = require('util').inspect;
var FIXTURES_ROOT = __dirname + '/fixtures/';
var t = 0,
group = path.basename(__filename, '.js') + '/';
var tests = [
{ source: 'nested',
opts: { boundary: 'AaB03x' },
chsize: 32,
nparts: 2,
what: 'One nested multipart'
},
{ source: 'many',
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' },
chsize: 16,
nparts: 7,
what: 'Many parts'
},
{ source: 'many-wrongboundary',
opts: { boundary: 'LOLOLOL' },
chsize: 8,
nparts: 0,
dicerError: true,
what: 'Many parts, wrong boundary'
},
{ source: 'many-noend',
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' },
chsize: 16,
nparts: 7,
npartErrors: 1,
dicerError: true,
what: 'Many parts, end boundary missing, 1 file open'
},
{ source: 'nested-full',
opts: { boundary: 'AaB03x', headerFirst: true },
chsize: 32,
nparts: 2,
what: 'One nested multipart with preceding header'
},
{ source: 'nested-full',
opts: { headerFirst: true },
chsize: 32,
nparts: 2,
setBoundary: 'AaB03x',
what: 'One nested multipart with preceding header, using setBoundary'
},
];
function next() {
if (t === tests.length)
return;
var v = tests[t],
fixtureBase = FIXTURES_ROOT + v.source,
n = 0,
buffer = Buffer.allocUnsafe(v.chsize),
state = { parts: [], preamble: undefined };
var dicer = new Dicer(v.opts),
error,
partErrors = 0,
finishes = 0;
dicer.on('preamble', function(p) {
var preamble = {
body: undefined,
bodylen: 0,
error: undefined,
header: undefined
};
p.on('header', function(h) {
preamble.header = h;
if (v.setBoundary)
dicer.setBoundary(v.setBoundary);
}).on('data', function(data) {
// make a copy because we are using readSync which re-uses a buffer ...
var copy = Buffer.allocUnsafe(data.length);
data.copy(copy);
data = copy;
if (!preamble.body)
preamble.body = [ data ];
else
preamble.body.push(data);
preamble.bodylen += data.length;
}).on('error', function(err) {
preamble.error = err;
}).on('end', function() {
if (preamble.body)
preamble.body = Buffer.concat(preamble.body, preamble.bodylen);
if (preamble.body || preamble.header)
state.preamble = preamble;
});
});
dicer.on('part', function(p) {
var part = {
body: undefined,
bodylen: 0,
error: undefined,
header: undefined
};
p.on('header', function(h) {
part.header = h;
}).on('data', function(data) {
if (!part.body)
part.body = [ data ];
else
part.body.push(data);
part.bodylen += data.length;
}).on('error', function(err) {
part.error = err;
++partErrors;
}).on('end', function() {
if (part.body)
part.body = Buffer.concat(part.body, part.bodylen);
state.parts.push(part);
});
}).on('error', function(err) {
error = err;
}).on('finish', function() {
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times'));
if (v.dicerError)
assert(error !== undefined, makeMsg(v.what, 'Expected error'));
else
assert(error === undefined, makeMsg(v.what, 'Unexpected error: ' + error));
var preamble;
if (fs.existsSync(fixtureBase + '/preamble')) {
var prebody = fs.readFileSync(fixtureBase + '/preamble');
if (prebody.length) {
preamble = {
body: prebody,
bodylen: prebody.length,
error: undefined,
header: undefined
};
}
}
if (fs.existsSync(fixtureBase + '/preamble.header')) {
var prehead = JSON.parse(fs.readFileSync(fixtureBase
+ '/preamble.header', 'binary'));
if (!preamble) {
preamble = {
body: undefined,
bodylen: 0,
error: undefined,
header: prehead
};
} else
preamble.header = prehead;
}
if (fs.existsSync(fixtureBase + '/preamble.error')) {
var err = new Error(fs.readFileSync(fixtureBase
+ '/preamble.error', 'binary'));
if (!preamble) {
preamble = {
body: undefined,
bodylen: 0,
error: err,
header: undefined
};
} else
preamble.error = err;
}
assert.deepEqual(state.preamble,
preamble,
makeMsg(v.what,
'Preamble mismatch:\nActual:'
+ inspect(state.preamble)
+ '\nExpected: '
+ inspect(preamble)));
assert.equal(state.parts.length,
v.nparts,
makeMsg(v.what,
'Part count mismatch:\nActual: '
+ state.parts.length
+ '\nExpected: '
+ v.nparts));
if (!v.npartErrors)
v.npartErrors = 0;
assert.equal(partErrors,
v.npartErrors,
makeMsg(v.what,
'Part errors mismatch:\nActual: '
+ partErrors
+ '\nExpected: '
+ v.npartErrors));
for (var i = 0, header, body; i < v.nparts; ++i) {
if (fs.existsSync(fixtureBase + '/part' + (i+1))) {
body = fs.readFileSync(fixtureBase + '/part' + (i+1));
if (body.length === 0)
body = undefined;
} else
body = undefined;
assert.deepEqual(state.parts[i].body,
body,
makeMsg(v.what,
'Part #' + (i+1) + ' body mismatch'));
if (fs.existsSync(fixtureBase + '/part' + (i+1) + '.header')) {
header = fs.readFileSync(fixtureBase
+ '/part' + (i+1) + '.header', 'binary');
header = JSON.parse(header);
} else
header = undefined;
assert.deepEqual(state.parts[i].header,
header,
makeMsg(v.what,
'Part #' + (i+1)
+ ' parsed header mismatch:\nActual: '
+ inspect(state.parts[i].header)
+ '\nExpected: '
+ inspect(header)));
}
++t;
next();
});
fs.createReadStream(fixtureBase + '/original').pipe(dicer);
}
next();
function makeMsg(what, msg) {
return '[' + group + what + ']: ' + msg;
}
process.on('exit', function() {
assert(t === tests.length,
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests'));
});

@ -0,0 +1,4 @@
require('fs').readdirSync(__dirname).forEach(function(f) {
if (f.substr(0, 5) === 'test-')
require('./' + f);
});

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save